Haskell 为什么';t产品[0..]评估为0“;“立即”吗;?

Haskell 为什么';t产品[0..]评估为0“;“立即”吗;?,haskell,lazy-evaluation,Haskell,Lazy Evaluation,我试图理解懒惰。因为0乘以任何数字都是0,所以乘积[0..]不应该计算为0吗?我还尝试了foldl(*)1[0..],并将我自己的产品定义为 myProduct 0 _ = 0 myProduct _ 0 = 0 myProduct a b = a*b 为什么不在找到0后立即停止折叠?因为乘法运算符不知道它正在链接,而折叠函数不知道乘法运算符对任何参数的特定行为。有了这样的组合,它需要用尽列表来完成折叠。事实上,由于这个原因,在无限列表上根本不起作用。是的,因为它可以从列表的开头展开函数 fo

我试图理解懒惰。因为0乘以任何数字都是0,所以
乘积[0..]
不应该计算为0吗?我还尝试了
foldl(*)1[0..]
,并将我自己的产品定义为

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

为什么不在找到0后立即停止折叠?

因为乘法运算符不知道它正在链接,而折叠函数不知道乘法运算符对任何参数的特定行为。有了这样的组合,它需要用尽列表来完成折叠。事实上,由于这个原因,在无限列表上根本不起作用。是的,因为它可以从列表的开头展开函数

foldl (*) 1 [0..] -> (((..(((1*0)*1)*2)*3....)*inf
在foldl情况下,最外层的乘法永远找不到,因为列表是无限的。因此,它不能按照链条得出结果为零的结论。它可以,而且确实,计算列表中的乘积,该乘积恰好保持为零,但它不会终止。如果您使用,您可以看到这些中间产品

foldr (*) 1 [0..] -> 0*(1*(2*(3*((...((inf*1)))...)))
foldr案例中最外层的乘法会立即找到,因为列表的其余部分实际上是一个懒惰的thunk。它只运行一个步骤:

foldr (*) 1 [0..] -> 0*(foldr (*) 1 [1..])
因此,由于自定义乘法运算符
myProduct
在第二个参数中不严格,如果第一个参数为零,
foldr myProduct 1[0..]
可以终止

作为旁注,prelude产品功能仅限于有限列表(并且可以使用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还有一个原因;正如我们在expansions和scanl函数中看到的,左折叠可以在使用列表时进行计算。如果操作符没有快捷方式,则右折叠需要构建一个与列表本身一样大的表达式,甚至可以开始计算。这种差异是因为在严格情况下,最内层的表达式开始计算,而最外层的表达式产生结果,允许使用惰性情况。在Haskell wiki中,可能比我解释得更好,甚至提到模式匹配(您用来描述myProduct中的快捷方式)可能非常严格

如果切换前两行:

myProduct _ 0 = 0
myProduct 0 _ = 0
myProduct a b = a*b
第二个参数将始终在第一个参数之前求值,无限foldr将不再工作


由于不可能定义一个对两个参数都惰性工作的
myProduct
(如果第一个参数为0,则不计算第二个参数;如果第二个参数为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时立即停止。

有人可能会认为情况并非总是如此:
foldl(*)1[0,未定义]
或者,NaN如何?因为,在IEEE浮点运算中,任何*NaN=NaN。特别是,0*NaN=NaN。
foldr(*)1[0..]
对我来说不是一步计算的。使用
myProduct
时也不需要。我错过了什么?不确定。对于我来说,我的产品是这样的:
让我的产品a b=案例(a,b)为(0,)->0;(_,0) -> 0; (x,y)->x*y
后接
foldr myProduct 1[0..]
在ghci中生成0。真是个混蛋!如果你改变了案例的顺序(或者我发布的案例中的模式),它就不再有效了!有什么解释吗?有。模式匹配严格;如果必须检查
\u0
,则必须确定第二个参数是否为零,这意味着必须对其进行计算。快捷方式必须在第一个模式中。您可以编写一个右折叠,该折叠使用常量空格,并在找到0后立即终止。诀窍是折叠一个包含三个参数而不是两个参数的函数。你知道怎么做吗?