我试图找出递归与List Comprehension / Monadic操作相结合如何导致空间泄漏。
我有一个小测试程序:
module Main where
permute :: [a] -> Integer -> [[a]]
permute _ 0 = [[]]
permute xs' n = [x:xs | x <- xs', xs <- permute xs' (n-1)]
chars1 = ['0'..'9']
main = do
putStrLn $ (permute chars1 10000)!!100000000 -- Leaks
print $ [1..]!!100000000 -- Does not leak
现在,我的理解是函数permute
可以扩展为
xs' >>= \x -> (permute xs' (n-1) >>= \xs -> (x:xs))
在检索结果之前,会堆叠很多(permute xs' n)
。我理解正确吗?如果是这样,我该怎么做才能确保功能不泄漏?
答案 0 :(得分:2)
这段代码有点奇怪,因为permute
实际上并不是......好吧......但是假设它是你想要的,它的实际执行方式与你的[0..] !! 10000
示例大致相同,只是计算第10000个选择是你的代码中的更多工作!
重要的是你的代码 懒惰,我们可以很容易地计算permute [0..] 10
的第一个元素,即使它们中有很多很明显。
你可能会想到的是,当你实际运行它时,生成任何输出需要很长时间,即使你可能希望它会产生第一个字符......等待一点..产生第二个字符..等一下..等等。这在Haskell意义上并不是真正的“泄漏”,在这种情况下无法避免。你的列表基本上是用
构建的map ('0':) (permute chars (n - 1)) ++ ... ++ map ('9':) (permute chars (n - 1))
问题在于,为了知道第一个字符,您需要确定要从中选择哪个“块”,这涉及计算块的长度,这涉及完全评估permute chars (n - 1)
。那么你将会依次强制每个递归调用,直到你在结果列表中有足够的元素来评估对!!
的调用。这并不意外。但是,您可以通过一个相当简单的技巧显着加快此代码的速度:反转列表推导中的x
和xs
。
permute :: [a] -> Integer -> [[a]]
permute _ 0 = [[]]
permute xs' n = [x:xs | xs <- permute xs' (n-1), x <- xs']
请注意,增加的共享理论上可能会导致内存使用量的增加(有些内容可能不会在此版本中尽快完成),但我无法想出一个非荒谬的程序来实现这一点。 / p>