如何停止GHCi中的无限评估?

mkU*_*tra 15 haskell ghci

当我运行类似:

Prelude> cycle "ab"
Run Code Online (Sandbox Code Playgroud)

我可以看到“ ab”的无限印刷。要停止它,我只需使用Ctrl+即可c。而且有效。

当我跑步时:

Prelude Data.List> nub $ cycle "ab"
Run Code Online (Sandbox Code Playgroud)

我无法阻止它。

题:

  • 为什么会这样呢?
  • 如何停止此操作?

更新:

 Ubuntu: version 18.10  
 GHCi:   version 8.2.2
Run Code Online (Sandbox Code Playgroud)

Zet*_*eta 12

好问题!但是,由于如何在GHCI中终止执行?已经专注于您的第二部分,在此不再重复。相反,让我们专注于第一个。

为什么会这样呢?

GHC积极优化循环。如果没有分配它甚至是一个已知的错误,它将进一步优化它们:

19.2.1。GHC中的错误

  • GHC的运行时系统实现了协作式多任务处理,仅当程序分配时,上下文切换才可能发生。这意味着未分配的程序可能永远不会进行上下文切换。对于使用STM的程序尤其如此,在观察到不一致的状态后,它们可能会死锁。有关更多讨论,请参见Trac#367。[强调我的]

    如果遇到这种情况,则可能需要使用编译受影响的模块-fno-omit-yields(请参见-f *:平台无关标志)。该标志确保在每个函数入口点插入屈服点(以牺牲性能为代价)。

如果检查-fomit-yields,我们发现:

-fomit-yields

默认值:启用屈服点

告诉GHC在不执行分配时忽略堆检查。尽管这将二进制大小提高了约5%,但这也意味着在紧密的非分配循环中运行的线程不会及时被抢占。如果始终能够中断此类线程很重要,则应关闭此优化。如果需要保证可中断性,请考虑在关闭此优化的情况下重新编译所有库。[强调我的]

nub $ cycle "ab"尽管last $ repeat 1是一个更明显的非分配示例,但它是一个紧密的非分配循环。

“启用屈服点”具有误导性:-fomit-yields默认情况下启用。作为标准库编译-fomit-yields在标准库中的所有功能铅紧,非分配循环可能会显示在该GHCI行为,因为你永远不重新编译。

我们可以使用以下程序进行验证:

-- Test.hs
myLast :: [a] -> Maybe a
myLast [x]    = Just x
myLast (_:xs) = myLast xs
myLast _      = Nothing

main = print $ myLast $ repeat 1
Run Code Online (Sandbox Code Playgroud)

C-c如果我们在GHCi中运行它而无需事先编译,则可以使用它来退出:

$ ghci Test.hs
[1 of 1] Compiling Main             ( Test.hs, interpreted )
Ok, one module loaded.
*Main> :main            <pressing C-c after a while>
Interrupted.
Run Code Online (Sandbox Code Playgroud)

如果我们编译它,然后在GHCi中重新运行它,它将挂起:

$ ghc Test.hs
[1 of 1] Compiling Main             ( Test.hs, Test.o )
Linking Test.exe ...

$ ghci Test.hs
Ok, one module loaded.
*Main> :main
<hangs indefinitely>
Run Code Online (Sandbox Code Playgroud)

请注意,-dynamic如果您不使用Windows,则需要这样做,否则GHCi将重新编译源文件。但是,如果使用-fno-omit-yield,我们突然可以再次退出(在Windows中)。

我们可以使用另一个小片段再次验证这一点:

Prelude> last xs = case xs of [x] -> x ; (_:ys) -> last ys
Prelude> last $ repeat 1
^CInterrupted
Run Code Online (Sandbox Code Playgroud)

由于ghci不使用任何优化,因此它也不使用-fomit-yield(因此已-fno-omit-yield启用)。我们的的新变体所last产生的行为与Prelude.last未使用进行编译的行为相同fomit-yield

现在我们知道了为什么会发生这种情况,我们知道在使用编译标准库时,我们将在整个标准库中遇到这种行为-fomit-yield