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

17

当我运行类似以下的内容时:

Prelude> cycle "ab"

我可以看到无限打印"ab"。为了停止它,我只需使用Ctrl+c。这样就行了。

当我运行:

Prelude Data.List> nub $ cycle "ab"

我无法停止它。

问题:

  • 为什么会这样?
  • 我如何停止这个操作?

更新:

 Ubuntu: version 18.10  
 GHCi:   version 8.2.2

1
我可以按照描述使用 GHCi 8.4.4 复现这个问题。ghci 不会对重复的 sigint 或 sigterm 做出响应。类似的问题请参考这里 - that other guy
1
你可以打开另一个终端窗口,找到正在运行的ghci命令的进程ID并将其杀死。 - Bob Dalgleish
2
还有一点要注意:在Linux上应该可以使用 CTRL+Z 来中止执行。https://dev59.com/a10Z5IYBdhLWcg3w8kL3 - Lorenzo
@thatotherguy 在 GHC 8.0.1 中无法重现类似的问题。 - Zeta
3
请在问题中添加您的GHCi版本和操作系统信息。 - Zeta
1个回答

13

好问题!但是,由于在GHCI中如何中止执行?已经着重讨论了您的第二部分,因此我们不需要在这里重复。相反,让我们关注第一个问题。

为什么会这样呢?

GHC会积极地优化循环,并在没有分配(甚至是已知的错误)的情况下进一步优化它们:

19.2.1. GHC中的错误

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

    如果您受到此影响,可以使用-fno-omit-yields编译受影响的模块(请参见-f*:平台无关标志)。此标志确保在每个函数入口处插入yield点(以牺牲一定的性能为代价)。

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

-fomit-yields

默认值:启用yield点

告诉GHC在不执行任何分配时省略堆检查。虽然这样可以将二进制文件大小提高约5%,但这也意味着在紧密、非分配循环中运行的线程将无法及时地被抢占。如果始终重要的是能够中断这些线程,则应关闭此优化。如果需要保证可中断性,则还应重新编译所有库并关闭此优化。[我强调的部分]

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

"启用yield点"这个名称是有误导性的:-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

如果我们在 GHCi 中运行程序 而未预先编译,我们可以使用C-c退出程序:

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

如果我们将其编译后在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>

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

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

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

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

现在我们知道了这种情况发生的原因,我们知道在整个标准库中将经历这种行为,因为标准库是使用-fomit-yield编译的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接