Подтвердить что ты не робот

Оператор назначения IO/Monadic, вызывающий ghci, чтобы взорваться для бесконечного списка

Рассмотрим следующую программу. Он работает вечно и не делает ничего полезного, но потребление памяти в ghci постоянное:

--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" теперь назначается x в блоке do test.

--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?

p.s. Я натолкнулся на это, пытаясь понять Взрыв памяти при написании ленивой байтовой строки в файл в ghci, и проблема там (я думаю) существенно отличается от вышеизложенного. Благодаря

4b9b3361

Ответ 1

Отказ от ответственности. Я не эксперт GHCi, а также не очень хорошо разбираюсь в ядре GHC. Теперь, когда я потерял доверие, попробуем понять, что происходит:

GHCi и CAFs

GHCi сохраняет все оцениваемые CAF:

Обычно любая оценка выражений верхнего уровня (иначе называемых CAF или константных аппликативных форм) в загруженных модулях сохраняется между оценками.

Теперь вы можете удивиться, почему существует такая большая разница между обеими версиями. Давайте посмотрим на ядро ​​с помощью -ddump-simpl. Обратите внимание, что вы можете отказаться от -dsuppress-all, когда вы сами выгружаете программы.

Дампы ваших программ

Неразрывная версия:

❯ 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..], почти в конце:

enumFrom $fEnumInt (I# 1))

Как вы можете видеть, список не является CAF. Но что произойдет, если вместо этого использовать взрывающуюся версию?

Взрывная версия

❯ 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 вводит новое выражение верхнего уровня.

Почему это происходит даже в оптимизированном коде?

Список все еще является выражением верхнего уровня:

main2
main2 = eftInt 1 2147483647

Забавно, что GHC на самом деле не создает бесконечный список, так как Int ограничен.

Как можно избавиться от утечки?

В этом случае вы можете избавиться от него, если вы разместите список в тесте:

test = do
   x <- print "test"
   rList [1..]

Это предотвратит создание GHC выражения верхнего уровня.

Однако я не могу дать общий совет по этому поводу. К сожалению, мой Haskell-fu еще недостаточно хорош.