GHC реализует Haskell как машину для сокращения графов. Представьте, что ваша программа представлена в виде графика, в котором каждое значение является узлом, а от него идут линии для каждого значения, от которого зависит это значение. За исключением того, что мы ленивы, поэтому вы действительно начинаете с одного узла - и для оценки этого узла GHC должен «войти» в него и открыть его для функции с аргументами. Затем он заменяет вызов функции телом функции и пытается уменьшить его настолько, чтобы привести его в нормальную форму и т. Д.
Выше очень волнистые, и я уверен, что для краткости вычеркну некоторые необходимые детали.
В любом случае, когда GHC вводит значение, оно обычно заменяет его черной дырой во время оценки узла (или, в зависимости от вашей терминологии, когда замыкание сокращается). Это имеет ряд целей. Во-первых, он закрывает потенциальную утечку пространства. Если узел ссылается на значение, которое больше нигде не используется, черная дыра позволяет этому значению собирать мусор даже во время оценки узла. Во-вторых, это предотвращает дублирование некоторых типов, поскольку в многопоточной среде два потока могут пытаться ввести одно и то же значение. Черная дыра вызовет блокировку второго потока, а не оценку уже оцениваемого значения. Наконец, это случается, чтобы учесть ограниченную форму обнаружения петель, поскольку, если поток пытается повторно войти в свою собственную черную дыру, мы можем выбросить исключение.
Вот немного более метафорического объяснения. Если у меня есть ряд инструкций, которые перемещают черепаху (в виде логотипа) по экрану, нет единого способа сказать, какую форму они будут создавать, или же эта форма завершится без их запуска. Но если во время их работы я заметил, что путь черепахи пересекся сам, я могу указать пользователю «ага! Черепаха пересекла свой путь!» Итак, я знаю, что черепаха достигла точки, в которой она была раньше - если путь - это схема, проходящая через оценку узлов графа, то это говорит о том, что мы находимся в цикле. Однако черепаха также может входить, например, в расширяющуюся спираль. И он никогда не закончится, но также никогда не пересечет свой предыдущий путь.
Итак, из-за использования черных дыр по разным причинам у нас есть некоторое представление о помеченном «пути», по которому прошла оценка. И если путь пересекает себя, мы можем сказать и выбросить исключение. Тем не менее, существует миллион способов расхождения, которые не связаны с самим пересечением пути. И в этих случаях мы не можем сказать, и не бросаем исключение.
Техническую информацию о текущей реализации черных дыр можно найти в выступлении Саймона Марлоу на недавнем семинаре для разработчиков Haskell "Планирование отложенной оценки на многоядерном компьютере" в нижней части http://haskell.org/haskellwiki/HaskellImplementorsWorkshop/2010.