以下代码为Quant
类型的量词计算数字树 1 ,类似于函数类型all
和any
:
treeOfNumbers :: [(Integer, Integer)]
treeOfNumbers =
[0..] >>= \ row ->
let
inc = [0 .. row]
dec = reverse inc
in
zip dec inc
type Quant = (Integer -> Bool) -> [Integer] -> Bool
check :: Quant -> (Integer, Integer) -> Bool
check q (m,n) =
q (\ d -> d - m > 0) [1 .. domN]
where
domN = m + n
genTree :: Quant -> [(Integer, Integer)]
genTree q =
filter (check q) treeOfNumbers
例如,take 10 $ genTree all
的值为
[(0,0),(0,1),(0,2),(0,3),(0,4),(0,5),(0,6),(0,7),(0,8),(0,9)]
但是,此代码似乎会导致内存泄漏。由于ghci的堆限制在100M,genTree all
在(0,1600)
周围被中断。限制在500M时,在变得非常慢之后会在(0,3950)
附近停止。
如何改进?我对Haskell的经验有限,只能猜测我treeOfNumbers
的实现可能是罪魁祸首; check
适用于大值,没有任何问题。
1 参见计算语义与功能编程(Jan van Eijck和Christina Unger),剑桥大学出版社,2010年,第157-159页。
答案 0 :(得分:6)
这里没有内存泄漏。您通过将其定义为顶级常量,明确告诉它保留整个元组列表。懒惰意味着它在需要之前不会产生价值,但这对保留价值没有帮助。 treeOfNumbers
不会被垃圾收集,直到垃圾收集器可以证明它永远不会被再次使用。一些粗略的数学表明,当(0,1600)
出现在列表中时,它将保持大约1,280,000个元组。这会占用大量的记忆。
答案 1 :(得分:3)
除非您绝对必须,否则不要为临时实体命名。
treeOfNumbers :: [(Integer, Integer)]
treeOfNumbers = [0..] >>= \ row -> zip [row, row-1 .. 0] [0 .. row]
= [p | row <- [0..], p <- zip [row, row-1 .. 0] [0 .. row]]
type Quant = (Integer -> Bool) -> [Integer] -> Bool
check :: Quant -> (Integer, Integer) -> Bool
check q (m,n) = q (> m) [1 .. m + n]
genTree :: Quant -> [(Integer, Integer)]
genTree q = filter (check q) treeOfNumbers
= [ p | p <- [p | row <- [0..], p <- zip [row, row-1 .. 0] [0 .. row]]
, check q p]
= [ p | row <- [0..], p <- zip [row, row-1 .. 0] [0 .. row]
, check q p]
= [ (m,n) | row <- [0..], (m,n) <- zip [row, row-1 .. 0] [0 .. row]
, q (> m) [1 .. m + n]]
现在genTree all
在GHCi内部几乎恒定的内存中运行,尽管它在某种程度上正在放缓。但它在几秒钟内轻松达到了3000。
使用顶级treeOfNumbers
,运行filter (check all) treeOfNumbers
会在35M内存中达到3000,并且比上面慢两倍。
所以它不仅仅是关于顶级名称;定义中的所有临时名称也会导致数据保留。
此外,请使用-O2标志对其进行编译,并将其作为独立可执行文件运行,以用于任何大型工作。