Haskell - 严格与非严格的foldl

时间:2012-11-23 17:34:53

标签: haskell lazy-evaluation fold strict

我对严格与非严格的定义有疑问。 Haskell wiki-laziness(http://en.wikibooks.org/wiki/Haskell/Laziness)在“黑盒严格性分析”一节中做出如下断言:

  

[假设函数f采用单个参数。]当且仅当f未定义导致打印错误并停止我们的程序时,函数f才是严格函数。

wiki将constid进行对比,分别显示非严格和严格的函数。

我的问题是,我认为foldl是以非严格的方式进行评估,造成不良的空间泄漏,而foldl是严格的。

然而,上述测试似乎断言foldl和foldl'都是严格的。如果它们的任何参数未定义,那么两个函数都会生成未定义:

> Data.List.foldl (+) undefined [1,2,3,4]
    Prelude.undefined
> Data.List.foldl' (+) 0 undefined
    Prelude.undefined
> Data.List.foldl' (+) undefined [1,2,3,4]
    Prelude.undefined
> Data.List.foldl (+) 0 undefined
    Prelude.undefined

有人可以解释一下我缺少的东西吗?

谢谢!

2 个答案:

答案 0 :(得分:8)

更好的定义是:如果在未定义此参数的情况下未定义函数,则该函数在参数中被认为是严格的。

让我们看看foldl的以下定义:

 foldl f s [] = s
 foldl f s (x:xs) = foldl f (f s x) xs

由此可以推断出以下内容:

  1. f中并不严格,因为f的值在第一个等式中并不重要。
  2. s中也不严格,因为列表可能是非空的,f在第一个参数中可能是非严格的(想想flip const)。
  3. 但是,在list参数中是严格的,因为无论fs是什么,这个参数必须被评估为所谓的弱头普通形式 。如果你可以告诉最外层的构造函数,代数数据类型的值在WHNF中。换句话说,foldl无法避免查看list参数是否为空列表。因此,至少到目前为止它必须评估列表值。但如果该值未定义,则2个等式中没有一个适用,因此foldl的这个应用本身是未定义的。
  4. 此外,由于foldl在累加器s中不严格,我们还可以了解为什么在许多情况下使用foldl是个坏主意:系统看不到实际评估术语f s x的原因,因为它被传递给相应参数中不严格的函数。实际上,如果对它进行评估,那将违反非严格性原则。因此,根据列表的长度,累加器中会累积一个巨大的thunk:

    ((((s `f` x_1) `f` x_2) `f` x_3) ....) `f` x_n)
    

    现在,如果f本身在其左侧参数中是严格的,如(+),那么任何有必要评估foldl结果的尝试都需要与列表长度成比例的堆栈空间。对于f ... x_n的评估,其中...代表左侧必须首先评估左侧,依此类推,然后评估为f s x_1并返回。

    因此我们拥有它

    foldl (flip const) 0 [1..n]
    

    n,而

    foldl const 0 [1..n]
    

    将堆叠溢出足够大n。这是一个确定的指标,在​​这种情况下,人类在计算方面比机器更好,因为在第二个例子中,列表的长度和内容完全无关紧要,大多数人会立即结果必须是0。

答案 1 :(得分:4)

你引用的wiki部分假设你正在处理一个带有单个参数的函数。 foldl的情况不同,首先是因为它采用了多个参数,但更重要的是因为其中一个参数是一个函数。让我们看一些例子,看看foldl在其参数中的确切时间。 (请注意,以下内容也适用于foldl'。)

> foldl undefined 0 []
0
> foldl undefined 0 [1]
*** Exception: Prelude.undefined

对于空列表,foldl不需要传递给它的组合函数。在这种情况下,第一个论点并不严格。

> foldl (+) undefined [1, 2, 3]
*** Exception: Prelude.undefined
> foldl (+) 0 undefined
*** Exception: Prelude.undefined

当你传递(+)作为组合函数时,它在起始值和列表中是严格的。这是另一个例子,具有不同的组合功能:

> foldl (flip (:)) undefined [1, 2, 3]
[3,2,1*** Exception: Prelude.undefined

有趣。起始值未定义,但似乎调用foldl在抛出异常之前产生了一些结果。那怎么样:

> let (x:xs) = foldl (flip (:)) undefined [1, 2, 3]
> x
3

没有例外。因此,我们可以从foldl中获取部分结果,而不会遇到可怕的Prelude.undefined。为什么呢?

嗯,唯一改变的是我们传递给foldl的组合功能。鉴于(+)的类型(大致)Int -> Int -> Intflip (:)的类型为[a] -> a -> [a]。虽然3 + ((2 + 1) + undefined)不在weak head normal form,但必须减少,以便评估undefined(3:(2:(1:undefined))) 是弱头正常形式,不需要进一步评估,特别是不需要评估undefined