我想要了解懒惰。因为0乘以任意数字是0,不应该{0} product [0..]
评估为0?我也尝试了foldl (*) 1 [0..]
,并将我自己的产品定义为
myProduct 0 _ = 0
myProduct _ 0 = 0
myProduct a b = a*b
为什么一旦找到0就不会停止折叠?
答案 0 :(得分:21)
因为乘法运算符不知道它被链接,并且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函数中看到的那样,左侧折叠可以在消耗列表时进行计算。如果操作符没有快捷方式,右侧折叠需要构建一个与列表本身一样大的表达式,甚至可以开始计算。这种差异是因为它是在严格情况下开始计算的最内层表达式,但是产生结果的最外层表达式允许惰性情况。 Haskell维基中的Lazy vs. non-strict可能会比我更好地解释,甚至提到用于描述myProduct中的快捷方式的模式匹配可能是严格的。
答案 1 :(得分:2)
如果切换前两行:
myProduct _ 0 = 0
myProduct 0 _ = 0
myProduct a b = a*b
第二个参数将始终在第一个参数之前进行评估,无限折叠器将不再起作用。
由于不可能定义一个对两个参数都懒惰的myProduct
(如果第一个为0则不评估第二个,如果第二个为0则不评估第一个)也许我们最好有{{} 1}}总是评估它们的论点。
答案 2 :(得分:0)
你可以这么做:
myproduct xs = foldr op id xs 1
where
op x r acc = if x==0 then 0 else acc `seq` r (acc*x)
这是一个右对折,它将左边的数字乘以常数空间,并在遇到0时立即停止。