Отличный вопрос! Однако, поскольку Как прервать выполнение в 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
Мы можем использовать 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.
Если мы скомпилируем и затем повторно запустим его в 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>
Обратите внимание, что вам нужно -dynamic
, если вы не используете Windows, иначе 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
.