我有以下代码:
{-# 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)。似乎它归结为整体调用,而这又归结为toInteger。严格分析器不知何故无法推断转换是严格的,这对我来说似乎很奇怪。
答案 0 :(得分:13)
对编辑的回应:O(N)空间泄漏对于累积参数的危险是众所周知的,至少对Haskell程序员来说是这样。什么应该是众所周知的,但不是因为 语言,你应该永远不要相信优化器为程序的空间和时间行为提供渐近保证。< / strong>我不明白我自己编写的简单优化器的含义,更不用说像GHC的前端那样毛茸茸的东西,使用严格分析器,内联器和其他所有东西。
关于你的两个问题,
为什么GHC的严格性分析器在 优化非常相似的代码时不优化这个特定的代码?
谁知道? (也许Simon PJ知道,也许不是。)如果你关心性能,你不应该依赖严格性分析器。这带来了第二个隐含的问题:
如何避免此函数和使用累积参数的所有其他函数的O(N)空间成本?
通过在累积参数上加上严格注释,强制在每次尾递归调用时对它们进行求值。
答案 1 :(得分:2)
我认为你正在以错误的方式看待这个问题。考虑以下,愚蠢的代码片段
let x = [undefined]
let y = map toInteger x
如果我们评估
y == []
我们得到False
,而如果我们评估
head y
我们收到undefined
个例外。没有理由将map
或y
与[]
进行比较,因为x
的唯一元素未定义。这是非严格性的本质。