Writer monad中的Memoization

时间:2014-03-02 15:30:24

标签: haskell monads memoization writer-monad

注意我只是想了解下面显示的这段特殊代码中发生了什么。我知道这可能不是解决问题的最佳方法。

我正在尝试使用具有memozied fibonacci函数的惰性Writer monad来计算调用函数的次数。该函数快速返回正确的值,但Writer环境永远不会返回,并且不使用任何CPU或内存。

import Control.Monad.Writer.Lazy as W

fib :: Int -> Writer (Sum Int) Int
fib = let fibs = mapM fib' [0..]
          fib' 0 = return 0
          fib' 1 = return 1
          fib' n = liftM2 (+) (fib $ n-1) (fib $ n-2)
      in \n -> tell (Sum 1) >> fibs >>= return . (!!n)

Prelude W> runWriter $ fib 51
(20365011074,Sum {getSum = Interrupted.

有人可以解释发生了什么吗?为什么环境没有返回值?

修改

无限列表[0..]不是问题所在。我尝试将其替换为[0..10][0..n]这样的有限列表,但问题仍然存在。 如果无限列表是问题,它将是一个非常耗费内存的计算,这就是为什么我上面提到它不会消耗任何令我困惑的CPU或内存。

我认为,由于懒惰,在评估fib函数的节点时,某处会出现死锁。

1 个答案:

答案 0 :(得分:3)

问题是mapM fib' [0..]。这是一个有效的计算,可以计算monad中的无限列表,对于它,它还需要组合无数个效果,在本例中为tell (Sum 1)。感谢Writer的懒惰,你可以访问结果,但是monoid部分内的计数永远不会完成。

更新:即使您使列表有限,它仍然无效。问题是mapM fib' [0..10]表示“Fibonacci数列表和计算它们所需的调用总数”。因此,在您的表达式tell (Sum1) >> fibs >>= ...中,您始终会将所有来电的总数添加到计数器中,这显然是您不想要的。

此外,它创建了一个无限循环:对fib的任何调用都会调用fib',它会计算所有元素的调用次数,从而调用(以及其他调用)fib' 2,再次调用fib。仅当您将列表限制为[0..1]时,无限递归才会停止。同样,问题是mapM结合了给定monadic计算的所有效果。

相反,你需要这样的东西:

import Control.Monad.Writer.Lazy as W

fib :: Int -> Writer (Sum Int) Int
fib = let fibs = map fib' [0..] -- <<<<
          fib' 0 = return 0
          fib' 1 = return 1
          fib' n = liftM2 (+) (fib $ n-1) (fib $ n-2)
      in \n -> tell (Sum 1) >> fibs !! n -- <<<<

这里fibs :: [Writer (Sum Int) Int],因此对于每个元素,它包含计算所需的结果和调用次数。