Почему продукт [0..] не оценивается до 0 "мгновенно"?

Я пытаюсь понять лень. Поскольку 0, умноженное на любое число, равно 0, не следует product [0..] оценивать 0? Я также попробовал foldl (*) 1 [0..] и определить свой собственный продукт как

myProduct 0 _ = 0
myProduct _ 0 = 0
myProduct a b = a*b

Почему не прекращается сбрасывание, как только будет найдено 0?

Ответы

Ответ 1

Потому что оператор умножения не знает, что он получает цепочку, а функция fold не знает размножения конкретного поведения оператора для любого аргумента. С этой комбинацией ей необходимо вычеркнуть список, чтобы закончить сгиб. Фактически, по этой причине foldl вообще не работает в бесконечных списках. foldr, потому что он может расширить функцию из главы списка.

foldl (*) 1 [0..] -> (((..(((1*0)*1)*2)*3....)*inf

Внешнее умножение в случае foldl никогда не может быть найдено, потому что список бесконечен. Поэтому он не может следовать цепочке, чтобы закончить результат равен нулю. Он может и делает, вычисляет продукт по списку, и этот продукт остается нулевым, но он не прекращается. Если вы используете scanl, вы можете видеть эти промежуточные продукты.

foldr (*) 1 [0..] -> 0*(1*(2*(3*((...((inf*1)))...)))

Наибольшее умножение в случае foldr обнаруживается немедленно, потому что остальная часть списка фактически оставлена ​​как ленивый thunk. Он выполняет только один шаг:

foldr (*) 1 [0..] -> 0*(foldr (*) 1 [1..])

Так как ваш пользовательский оператор умножения myProduct не является строгим во втором аргументе, если первый аргумент равен нулю, foldr myProduct 1 [0..] может завершиться.

В качестве побочного примечания функция продукта прелюдии ограничена конечными списками (и может быть реализована с помощью foldl). Даже если он использует foldr, он, вероятно, не будет сокращен, потому что стандартный оператор умножения является строгим; иначе было бы дорого вычислительно дорого в общем случае, когда продукты не являются ни нулевыми, ни цепными.

-- sum and product compute the sum or product of a finite list of numbers.

sum, product     :: (Num a) => [a] -> a
sum              =  foldl (+) 0  
product          =  foldl (*) 1

Кроме того, есть причина, по которой он не использует foldr; как мы могли видеть в функциях расширения и scanl, левые складки могут вычисляться по мере того, как они потребляют список. Правильная сгиб, если оператор не работает, должен построить выражение, такое же самое, как сам список, даже начать вычисление. Это различие состоит в том, что это самое внутреннее выражение, которое начинает вычисление в строгом случае, но самое внешнее выражение, которое дает результат, позволяя ленивый случай. Lazy vs. non-strict в вики Haskell может объяснить лучше, чем я могу, и даже упоминает, что соответствие шаблонов, которое вы использовали для описания ярлык в myProduct, может быть строгим.

Ответ 2

Если вы переключите первые две строки:

myProduct _ 0 = 0
myProduct 0 _ = 0
myProduct a b = a*b

второй аргумент всегда будет оцениваться до первого, а бесконечный foldr больше не будет работать.

Так как невозможно определить a myProduct, который лениво работает для обоих аргументов (не оценивая второй, если первый равен 0 и не оценивает первое, если второе равно 0), возможно, нам лучше, если * всегда оцените оба аргумента.

Ответ 3

Вы можете иметь это:

myproduct xs = foldr op id xs 1
  where
    op x r acc = if x==0 then 0 else acc `seq` r (acc*x)

Это правая складка, которая умножает числа слева, работает в постоянном пространстве, и останавливается, как только встречается 0.