为什么Haskell不能优化这个? (在Maybe monad中没有任何东西被不必要地传播。)

时间:2015-03-29 21:04:17

标签: haskell

让我们从

开始
boom :: Int -> Maybe a -> Maybe a
boom 0 x = x
boom n x = boom (n-1) (x >>= (\y -> Just y))

这是一个简单的函数,只是将>>= Maybe值重复推入一个简单的\y -> Just y函数。

现在,程序

main = do
    let z = boom 10 (Nothing :: Maybe Int)
    putStrLn $ show z

一瞬间跑得很快。但是,程序

main = do
    let z = boom 10000000 (Nothing :: Maybe Int)
    putStrLn $ show z

需要几秒钟才能完成,即使我使用ghc -O进行编译(GHC 7.8.3)。

这意味着Haskell无法对此进行优化。 Nothing被反复推入一个函数,即使它没有必要这样做。

我的问题是,为什么?为什么不能推断Nothing在重复推特中总是以Nothing结尾?换句话说,为什么它不能在第一个Nothing 时立即短路

3 个答案:

答案 0 :(得分:7)

你的一个很好的例子是一个慢的函数,因为它是尾递归的。在严格的语言中,尾递归函数通常是首选,因为它们通常会带来更好的性能(在时间和空间上)。在懒惰的尾部递归并不是那么有益。实际上,函数的非尾递归变体是:

boom :: Int -> Maybe a -> Maybe a
boom 0 x = x
boom n x = x >>= (\y -> boom (n-1) (Just y))

nx时,上述内容仍会循环Just something次。但是,它将在常量空间中执行此操作,而不像在第二个参数中构建大型thunk的原始代码。更好的是,当xNothing时,上述代码将立即返回。

我确实意识到这并没有真正回答你关于“为什么”GHC无法对此进行优化的问题。但希望,它可以表明这些优化非常微妙,并且通常涉及归纳推理。期望编译器对此进行优化可能有点过分。

答案 1 :(得分:1)

您需要使用-fforce-recomp

强制重新编译

使用ghc编译并获得1.44秒

使用ghc -O编译并获得1.44秒

使用ghc -O -fforce-recomp编译并获得0.00s到0.04s

请注意

它仍无法与boom maxBound (Nothing :: Maybe Int)

一起使用

你会等很长时间。

答案 2 :(得分:0)

因为boom首先展开了n-1

所以boom 100 x = boom (100-1) ... = boom (100-1-1) ...等...

你可以尝试交换繁荣的论点(但我不确定会改变什么)。