首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >IO/一元赋值运算符导致ghci在无限列表中爆炸

IO/一元赋值运算符导致ghci在无限列表中爆炸
EN

Stack Overflow用户
提问于 2014-07-28 06:32:06
回答 1查看 349关注 0票数 17

考虑下面的程序。它永远运行,不做任何有用的事情,但是ghci中的内存消耗是恒定的:

代码语言:javascript
复制
--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..]

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

代码语言:javascript
复制
--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..]

为什么将print "test"更改为x <- print "test"会导致ghci崩溃?

附注:我在试图理解Memory exploding upon writing a lazy bytestring to file in ghci时遇到了这个问题,问题(我认为)本质上就是上述问题的提炼。谢谢

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2014-07-28 15:43:36

免责声明:我不是GHCi专家,也不擅长使用GHC核心。现在我已经失去了我的可信度,让我们试着理解发生了什么:

GHCi和CAFs

GHCi retains all evaluated CAFs

通常,在加载的模块中对顶级表达式(也称为CAF或常量应用形式)的任何求值都会在求值之间保留。

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

程序的转储

非拆分版本:

代码语言:javascript
复制
❯ 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

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

代码语言:javascript
复制
enumFrom $fEnumInt (I# 1))

如你所见,名单不是咖啡馆。但是,如果我们使用爆炸版本,会发生什么呢?

拆分版本

代码语言:javascript
复制
❯ 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

突然出现了一个新的顶级表达式,即lst_rq4,它生成列表。如前所述,GHCi保留了顶级表达式的计算结果,因此lst_rq4也将保留。

现在有一个丢弃评估的选项:

启用+r会导致在每次求值后丢弃顶级表达式的所有求值(它们在单个求值过程中仍会保留)。

但是,由于“它们在单个评估过程中仍然保留”,即使是:set +r在这种情况下也不会对您有所帮助。不幸的是,我无法回答为什么GHC引入了一个新的顶级表达式。

为什么在优化的代码中会发生这种情况呢?

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

代码语言:javascript
复制
main2
main2 = eftInt 1 2147483647

有趣的是,GHC实际上不会创建无限列表,因为Int是有界的。

怎样才能摆脱泄漏呢?

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

代码语言:javascript
复制
test = do
   x <- print "test"
   rList [1..]

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

然而,我真的不能对此给出一般的建议。不幸的是,我的Haskell-fu还不够好。

票数 10
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/24986296

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档