【问题标题】:Why doesn't product [0..] evaluate to 0 "instantly"?为什么产品 [0..] 不“立即”评估为 0?
【发布时间】:2015-05-28 09:45:44
【问题描述】:

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

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

为什么一找到 0 就不会停止折叠?

【问题讨论】:

  • 可以说并非总是如此:foldl (*) 1 [0, undefined]
  • 另外,NaN 怎么样?因为,在 IEEE 浮点中,任何东西 * NaN = NaN。特别是,0 * NaN = NaN。

标签: haskell lazy-evaluation


【解决方案1】:

因为乘法运算符不知道它被链接了,并且折叠函数不知道乘法运算符对任何参数的特定行为。使用这种组合,它需要用尽列表才能完成折叠。事实上,出于这个原因foldl 在无限列表上根本不起作用。 foldr 可以,因为它可以从列表的头部扩展函数。

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

foldl 情况下的最外层乘法永远找不到,因为列表是无限的。因此,它不能遵循链来得出结果为零的结论。它可以并且确实计算列表中的乘积,并且该乘积恰好保持为零,但它不会终止。如果您改用scanl,则可以看到这些中间产品。

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

foldr 情况下的最外层乘法立即被找到,因为列表的其余部分实际上是作为惰性重击留下的。它只运行一步:

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

因此,如果第一个参数为零,则因为您的自定义乘法运算符 myProduct 在第二个参数中不严格,foldr myProduct 1 [0..] 可以终止。

附带说明,prelude product 函数仅限于有限列表(并且可以使用 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 wiki 中的Lazy vs. non-strict 可能比我解释得更好,甚至提到您用来描述 myProduct 中的快捷方式的模式匹配可能很严格。

【讨论】:

  • foldr (*) 1 [0..] 不会对我进行一步评估。 myProduct 也没有。我错过了什么?
  • 不确定。 myProduct 对我有用:let myProduct a b = case (a,b) of (0,_) -> 0 ; (_,0) -> 0; (x,y) -> x*y 后跟 foldr myProduct 1 [0..] 在 ghci 中产生 0。
  • 枪之子!如果您更改案例的顺序(或我发布的案例中的模式),它将不再起作用!对此有何解释?
  • 是的。模式匹配严格;如果它必须检查_ 0,它必须确定第二个参数是否为零,这意味着它必须被评估。快捷方式必须在第一个模式中。
  • 您可以编写一个右折叠,它使用常量空间并在找到 0 时立即终止。诀窍是折叠一个采用 三个 参数而不是两个参数的函数。你能看出来吗?
【解决方案2】:

如果切换前两行:

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

第二个参数总是在第一个参数之前计算,并且无限折叠器将不再起作用。

由于不可能定义一个对两个参数都懒惰地工作的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 就停止。

【讨论】:

    猜你喜欢
    • 2021-11-01
    • 2019-05-24
    • 2018-08-16
    • 2016-08-25
    • 1970-01-01
    • 1970-01-01
    • 2013-09-02
    • 2011-07-04
    相关资源
    最近更新 更多