当我运行类似:

Prelude> cycle "ab"

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

当我跑步时:
Prelude Data.List> nub $ cycle "ab"

我无法阻止它。

问题:
  • 为什么会这样?
  • 如何停止此操作?

  • 更新:
     Ubuntu: version 18.10
     GHCi:   version 8.2.2
    

    最佳答案

    好问题!但是,由于How to abort execution in GHCI?已经专注于您的第二部分,因此在此不再重复。相反,让我们专注于第一个。

    为什么会这样呢?

    GHC积极地优化循环。如果没有分配that it's even a known bug,它将进一步优化它们:

    19.2.1。 GHC中的错误


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

    如果您对此感到不满,则可能需要使用 -fno-omit-yields 编译受影响的模块(请参见-f*: platform-independent flags)。此标志确保在每个函数入口点插入屈服点(以牺牲一些性能为代价)。


  • 如果检查 -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
    

    如果我们在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编译标准库时会遇到这种行为。

    关于haskell - 如何停止GHCi中的无限评估?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/55188787/

    10-13 02:31