Ответ 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, может быть строгим.