#haskell
#хаскелл #haskell
Вопрос:
Я играю с векторами и распакованными векторами с помощью довольно простой программы. Я перечисляю все целые числа, единственными факторами которых являются 2,3 и 5.
Я подумал, что попробую записать это на память Data.Vector
, что сработало и было очень просто. Так что я решил попробовать Data.Vector.Unboxed
. Однако он зависает, когда z равно [0..5]
, но не тогда, когда z равно [0..4]
, и я не уверен, почему. Разница между ними заключается в том, что 5
включает взаимно рекурсивный вызов.
Что здесь происходит не так?
import Data.Vector.Unboxed as UV
memoisingCandidateUV :: UV.Vector Bool
memoisingCandidateUV = UV.map isCandidateUV z
isCandidateUV :: Int -> Bool
isCandidateUV 0 = False
isCandidateUV n
| n2r == 0 = n2q == 1 || memoisingCandidateUV UV.! n2q
| n3r == 0 = n3q == 1 || memoisingCandidateUV UV.! n3q
| n5r == 0 = n5q == 1 || memoisingCandidateUV UV.! n5q
| otherwise = False
where
(n2q, n2r) = n `quotRem` 2
(n3q, n3r) = n `quotRem` 3
(n5q, n5r) = n `quotRem` 5
Ответ №1:
Все распакованные контейнеры строго настроены, что означает, что вы не можете использовать их для отложенного запоминания: прежде чем любой элемент будет доступен как записанный, вы должны оценить все из них.
Что касается того, почему это так: когда у вас есть обычный ленивый блок в Haskell, это поле, которое содержит эту информацию о том, что значение еще не находится в NF прямо сейчас, но чтобы сделать это так, чтобы вы могли выполнить этот фрагмент кода. После того, как это было сделано, поле в основном является просто указателем на результат.
Это отличная помощь во многих ситуациях, но это не для свободной памяти или производительности: это именно те издержки, которые устраняют распакованные контейнеры, напрямую сохраняя информацию о результате в плотном массиве.
Комментарии:
1. Почему распакованные контейнеры являются глубоко строгими? Я полагаю, что понимаю почему, но было бы полезно услышать это от кого-то, кто действительно знает.
Ответ №2:
В этом конкретном случае (все рекурсивные вызовы относятся к меньшим индексам и n
могут быть восстановлены из уже вычисленного вектора), вы можете использовать constructN (он работает итеративно, а не полагается на лень):
import Data.Vector.Unboxed as UV
memoisingCandidateUV :: UV.Vector Bool
memoisingCandidateUV = UV.constructN 100 isCandidateUV
isCandidateUV :: UV.Vector Bool -> Bool
isCandidateUV vec = result
where
n = UV.length vec
result
| n == 0 = False
| n2r == 0 = n2q == 1 || vec UV.! n2q
| n3r == 0 = n3q == 1 || vec UV.! n3q
| n5r == 0 = n5q == 1 || vec UV.! n5q
| otherwise = False
(n2q, n2r) = n `quotRem` 2
(n3q, n3r) = n `quotRem` 3
(n5q, n5r) = n `quotRem` 5
Комментарии:
1. Приятно! Хотя я полагаю, что это в значительной степени сводит на нет какое-либо преимущество в производительности по сравнению с коробочными векторами?
2. Он не копирует вектор на каждой итерации (если это ваше подозрение); он передает фрагмент
unsafeFreeze
уже созданного изменяемого вектора в функцию builder, что на самом деле безопасно, потому что этот фрагмент не будет мутирован дальше ( источник ).