注意我只是想了解下面显示的这段特殊代码中发生了什么。我知道这可能不是解决问题的最佳方法。
我正在尝试使用具有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
函数的节点时,某处会出现死锁。
答案 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]
,因此对于每个元素,它包含计算所需的结果和调用次数。