Haskell列表理解和空间泄漏

时间:2016-03-30 22:27:15

标签: haskell memory-leaks list-comprehension monads lazy-evaluation

我试图找出递归与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)。我理解正确吗?如果是这样,我该怎么做才能确保功能不泄漏?

1 个答案:

答案 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)。那么你将会依次强制每个递归调用,直到你在结果列表中有足够的元素来评估对!!的调用。这并不意外。但是,您可以通过一个相当简单的技巧显着加快此代码的速度:反转列表推导中的xxs

permute :: [a] -> Integer -> [[a]]
permute _ 0 = [[]]
permute xs' n = [x:xs | xs <- permute xs' (n-1), x <- xs']

请注意,增加的共享理论上可能会导致内存使用量的增加(有些内容可能不会在此版本中尽快完成),但我无法想出一个非荒谬的程序来实现这一点。 / p>