此代码对在顶级指定的两个(相互取消)无限列表的内容进行求和:
{-# language BangPatterns #-}
module Main where
unending1 :: [Int]
unending1 = cycle [1]
unending2 :: [Int]
unending2 = cycle [negate 1]
main :: IO ()
main = do
let summator :: Int -> [Int] -> [Int] -> Int
summator !acc (i1 : rest1) (i2 : rest2) =
if acc > 100
then acc -- never happens
else summator (acc+i1+i2) rest1 rest2
print (summator 0 unending1 unending2)
我编译此代码时没有优化和低堆大小,如下所示:
ghc -O0 -with-rtsopts="-M10m" Main.hs
我的直觉是这段代码会导致内存泄漏,因为求和函数会尝试“实现”两个列表,并且它们的头部都处于顶层,所以它们不会被丢弃
然而,当我执行程序时,它似乎无限期地运行而没有问题。
我错在哪里?
编辑。使用-ddump-simpl
检查生成的Core,列表似乎仍保持在最高级别。例如:
Result size of Tidy Core = {terms: 77, types: 52, coercions: 0}
-- RHS size: {terms: 5, types: 3, coercions: 0}
unending1 :: [Int]
[GblId, Str=DmdType]
unending1 =
cycle
@ Int (GHC.Types.: @ Int (GHC.Types.I# 1#) (GHC.Types.[] @ Int))
答案 0 :(得分:4)
正如Chi回答的那样,由于cycle
的定义,您的代码在恒定的空间中运行。
但是,即使使用ghc -O0
,它也会在cycle xs = xs ++ cycle xs
的恒定空间中运行,因为顶级thunk(常量应用形式,CAF-s)可以进行垃圾回收。闭包的信息表有“静态引用表”,它列出了静态闭包,以便
文档here。如果无法从GC根(包括线程状态对象的堆栈,也就是在我们的情况下执行中的main
的闭包)到达顶级thunks,则它们指向的堆对象将被丢弃。
答案 1 :(得分:3)
来自cycle :: [a] -> [a]
cycle [] = errorEmptyList "cycle"
cycle xs = xs' where xs' = xs ++ xs'
:
xs'
请注意,递归不涉及函数调用,而是列表值cycle
。
当完全强制时,它应该在内存中表示为循环链表,带有向后指针。然后只需要有限的内存。
尝试例如定义自己的cycle' xs = xs ++ cycle' xs
:
> let list1 :: [Int] ; list1 = cycle [1,2,3]
> list1 !! (4*10^9)
2
因为GHC不进行自动记忆,所以这应该在内存中生成一个无界限列表。
实际上,即使在GHCi(未经优化)中,我的机器上仍然保持在70M以下
> let list2 :: [Int] ; list2 = cycle' [1,2,3]
> list2 !! (4*10^7)
2
虽然爆炸(> 1GB):
sig Person {father: Person}
fact {
no p: Person | p in p.^father
}
pred Show {}
run Show