【问题标题】:Haskell - strict vs non-strict with foldlHaskell - 严格与非严格与 foldl
【发布时间】:2012-11-12 02:01:36
【问题描述】:

我有一个关于严格与非严格定义的问题。 Haskell wiki-book for Laziness (http://en.wikibooks.org/wiki/Haskell/Laziness) 在“黑盒严格性分析”部分下做出以下断言:

[假设函数 f 接受单个参数。] 函数 f 是严格函数当且仅当 f undefined 导致打印错误并停止我们的程序。

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

有人可以解释一下我缺少什么吗?

谢谢!

【问题讨论】:

    标签: haskell lazy-evaluation fold strict


    【解决方案1】:

    您引用的 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 -> Int 类型,flip (:) 具有[a] -> a -> [a] 类型。而3 + ((2 + 1) + undefined) 不在weak head normal form 中并且必须减少以便评估undefined,而(3:(2:(1:undefined))) 处于弱头正常形式,不需要进一步评估,特别是不需要评估undefined

    【讨论】:

    • 值得一提的是,在所有这些示例中,foldl'foldl 的行为方式完全相同。
    【解决方案2】:

    一个更好的定义是:如果一个函数在一个参数未定义的情况下是未定义的,则称该函数是严格的。

    让我们看下foldl的定义:

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

    由此可以推断出以下几点:

    1. f 中并不严格,因为fs 的值在第一个等式中并不重要。
    2. s 也不是严格的,因为列表可能是非空的,f 在它的第一个参数中可能是非严格的(想想flip const)。
    3. 然而,在 list 参数中它是严格的,因为无论 fs 是什么,这个参数都必须被评估为所谓的 weak head normal form。如果您可以告诉最外层的构造函数,则代数数据类型的值在 WHNF 中。换句话说, foldl 无法避免查看 list 参数是否为空列表。因此,它必须至少到目前为止评估列表值。但如果该值未定义,则 2 个等式都不适用,因此 foldl 的此应用程序本身是未定义的。

    此外,从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。

    【讨论】:

      猜你喜欢
      • 2014-01-02
      • 2017-03-29
      • 2016-07-24
      • 2011-07-02
      • 1970-01-01
      • 1970-01-01
      • 2020-11-26
      • 1970-01-01
      • 2011-03-09
      相关资源
      最近更新 更多