【问题标题】:Why toInteger :: Int -> Integer is lazy?为什么 toInteger :: Int -> Integer 是懒惰的?
【发布时间】:2010-05-01 12:57:43
【问题描述】:

我有以下代码:

{-# NOINLINE i2i #-}
i2i :: Int -> Integer
i2i x = toInteger x

main = print $ i2i 2

使用 -ddump-simpl 标志运行 GHC 会给出:

[Arity 1
 NoCafRefs
 Str: DmdType U(L)]
Main.i2i = GHC.Real.toInteger1

似乎从 Int 到 Integer 的转换是惰性的。为什么会这样 - 有没有什么情况下我可以拥有

(toInteger _|_ ::Int) /= _|_

?

编辑:这个问题更多地与 GHC 严格性分析器有关,而不是与懒惰本身有关。此代码源自探索标准均值函数:

--mean :: Integer -> Integer -> [Integer] -> Double
mean :: Integer -> Int -> [Integer] -> Double
mean acc n [] = fromIntegral acc / fromIntegral n
mean acc n (x:xs) = mean (acc + x) (n + 1) xs

main = print $ mean 0 0 [1..1000000]

此代码在 O(N) 空间上运行。当我取消注释第一行时,空间消耗变为 O(1)。似乎归结为 fromIntegral 调用,而后者又归结为 toInteger。严格分析器不知何故无法推断出转换是严格的,这对我来说似乎很奇怪。

【问题讨论】:

    标签: haskell lazy-evaluation


    【解决方案1】:

    响应您的编辑:O(N) 空间泄漏对累积参数的危险是众所周知的,至少对于 Haskell 程序员而言。应该众所周知但不为人所知的是,无论什么语言,您都应该永远不要相信优化器会为您的程序的空间和时间行为提供渐近保证。 我不明白我自己编写的简单优化器的含义,更不用说像 GHC 的前端那样毛茸茸的东西了,还有严格分析器、内联器等等。

    关于你的两个问题,

    为什么 GHC 的严格性分析器不优化这个特定的代码,而 优化了非常相似的代码?

    谁知道?? (也许 Simon PJ 知道,也许不知道。)如果你关心性能,你不应该依赖严格度分析器。这就引出了第二个隐含的问题:

    如何避免此函数以及使用累积参数的所有其他函数的 O(N) 空间成本?

    通过在累积参数上添加严格注释,强制在每次尾递归调用时对其进行评估。

    【讨论】:

    • “永远不要相信优化器会为你的程序的空间和时间行为提供渐近保证” 通常是正确的,但例外情况是像 Scheme 和 Erlang 这样的语言中的尾调用优化。
    • @Longpoke:在这些语言中,尾调用保证得到优化。因此,性能方面从来没有任何问题:如果编译器无法优化,它就是不兼容的。编译器不好!没有饼干!
    【解决方案2】:

    我认为你看错了。考虑以下愚蠢的代码片段

    let x = [undefined]
    let y = map toInteger x
    

    如果我们评估

     y == []
    

    我们得到False,而如果我们评估

     head y
    

    我们得到一个undefined 异常。应用map 或将y[] 进行比较没有理由仅仅因为x 的唯一元素未定义而产生分歧。这就是不严格的本质。

    【讨论】:

    • 请参阅编辑 - 我理解您的示例中的严格性,但这不是严格性本身,而是 GHC 如何推断优化的严格性。
    猜你喜欢
    • 2015-09-06
    • 2013-03-15
    • 1970-01-01
    • 1970-01-01
    • 2019-10-15
    • 1970-01-01
    • 2016-10-27
    • 2021-08-12
    • 2011-11-21
    相关资源
    最近更新 更多