IO/Monadic assign运算符导致ghci爆炸无限列表

art*_*lla 17 haskell memory-leaks ghci

考虑以下程序.它永远运行并没有任何用处,但ghci中的内存消耗是不变的:

--NoExplode.hs
module Main (main) where

test :: [Int] -> IO()
test lst = do
  print "test"
  rList lst

rList :: [Int] -> IO ()
rList [] = return ()
rList (x:xs) = do
  rList xs

main = do
  test [1..]
Run Code Online (Sandbox Code Playgroud)

现在考虑以上的以下简单修改版本.当这个程序在ghci中运行时,内存会爆炸.唯一的区别是print "test"现在分配给xdotest.

--Explode.hs
module Main (main) where

test :: [Int] -> IO()
test lst = do
  x <- print "test"
  rList lst

rList :: [Int] -> IO ()
rList [] = return ()
rList (x:xs) = do
  rList xs

main = do
  test [1..]
Run Code Online (Sandbox Code Playgroud)

为什么改变print "test",以x <- print "test"使ghci中炸掉?

ps当我试图理解在ghci中写一个懒惰的字节串到文件时,我遇到了这个问题,而那里的问题(我认为)基本上已经提到了上面.谢谢

Zet*_*eta 10

免责声明:我不是GHCi专家,也不是GHC核心的好人.既然我已经失去了信誉,那就让我们试着了解会发生什么:

GHCi和CAF

GHCi 保留所有评估的CAF:

通常,在评估之间保留对已加载模块中的顶级表达式(也称为CAF或常量应用表单)的任何评估.

现在您可能想知道为什么两个版本之间存在如此大的差异.让我们来看看核心-ddump-simpl.请注意,您可能希望-dsuppress-all在自己转储程序时删除.

转储你的程序

非爆炸版本:

? ghc SO.hs -ddump-simpl -fforce-recomp -O0 -dsuppress-all
[1 of 1] Compiling Main             ( SO.hs, SO.o )

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 29, types: 28, coercions: 0}

$dShow_rq2
$dShow_rq2 = $fShow[] $fShowChar

Rec {
rList_reI
rList_reI =
  \ ds_dpU ->
    case ds_dpU of _ {
      [] -> return $fMonadIO ();
      : x_aho xs_ahp -> rList_reI xs_ahp
    }
end Rec }

main
main =
  >>
    $fMonadIO
    (print $dShow_rq2 (unpackCString# "test"))
    (rList_reI (enumFrom $fEnumInt (I# 1)))

main
main = runMainIO main
Run Code Online (Sandbox Code Playgroud)

重要的部分是[1..]几乎在最后的位置:

enumFrom $fEnumInt (I# 1))
Run Code Online (Sandbox Code Playgroud)

如您所见,该列表不是CAF.但是如果我们使用爆炸版本会发生什么呢?

爆炸版

? ghc SO.hs -ddump-simpl -fforce-recomp -O0 -dsuppress-all
[1 of 1] Compiling Main             ( SO.hs, SO.o )

==================== Tidy Core ====================
Result size of Tidy Core = {terms: 32, types: 31, coercions: 0}

$dShow_rq3
$dShow_rq3 = $fShow[] $fShowChar

Rec {
rList_reI
rList_reI =
  \ ds_dpV ->
    case ds_dpV of _ {
      [] -> return $fMonadIO ();
      : x_ahp xs_ahq -> rList_reI xs_ahq
    }
end Rec }

lst_rq4
lst_rq4 = enumFrom $fEnumInt (I# 1)

main
main =
  >>=
    $fMonadIO
    (print $dShow_rq3 (unpackCString# "test"))
    (\ _ -> rList_reI lst_rq4)

main
main = runMainIO main
Run Code Online (Sandbox Code Playgroud)

突然出现了一个新的顶级表达式,即lst_rq4生成列表.如前所述,GHCi保留了对顶级表达式的评估,因此lst_rq4也将保留.

现在可以选择放弃评估:

启用+r导致在每次评估后丢弃顶级表达式的所有评估(在单次评估期间仍保留它们).

但由于"在单次评估期间仍然保留它们" :set +r,在这种情况下甚至无法帮助您.不幸的是,我无法回答为什么GHC引入了一个新的顶级表达式.

为什么这甚至会在优化的代码中发生?

该列表仍然是顶级表达式:

main2
main2 = eftInt 1 2147483647
Run Code Online (Sandbox Code Playgroud)

有趣的是,GHC实际上并没有创建无限列表,因为它Int是有界的.

如何摆脱泄漏?

在这种情况下,如果将列表放在 test 中,则可以删除它:

test = do
   x <- print "test"
   rList [1..]
Run Code Online (Sandbox Code Playgroud)

这将阻止GHC创建顶级表达式.

但是,我真的无法对此提出一般性建议.不幸的是,我的Haskell-fu还不够好.