Эти вики-страницы на Haskell, на которые вы ссылаетесь, старые, и я думаю, что, к сожалению, они написаны.Особенно прискорбно, что они смешивают CAF и суперкомбинаторы.Суперкомбинаторы интересны, но не связаны с GHC.CAF по-прежнему являются частью GHC и могут быть поняты без ссылки на суперкомбинаторы.
Итак, давайте начнем с суперкомбинаторов .Комбинаторы являются производными от комбинаторной логики и, при использовании здесь, состоят из функций, которые только применяют передаваемые друг к другу значения в той или иной форме, то есть объединяют свои аргументы.Самый известный набор комбинаторов - это S, K и I , которые вместе взяты по Тьюрингу.В этом контексте суперкомбинаторы - это функции, построенные только из переданных значений, комбинаторов и других суперкомбинаторов.Следовательно, любой суперкомбинатор может быть расширен путем замены в обычный старый комбинатор.
Некоторые компиляторы для функциональных языков (не GHC!) Используют комбинаторы и суперкомбинаторы в качестве промежуточных этапов компиляции.Как и с любой подобной технологией компилятора, причина для этого состоит в том, чтобы допустить оптимизационный анализ, который легче выполнять на таком упрощенном, минимальном языке.Одним из таких базовых языков, построенных на суперкомбинаторах, является epic .
константных аппликативных форм Эдвина Брейди, которые являются чем-то совершенно другим.Они немного более тонкие и имеют несколько ошибок.Их можно представить как аспект реализации компилятора, который не имеет отдельного семантического значения, но потенциально может оказать существенное влияние на производительность во время выполнения.Нижеследующее, возможно, не является идеальным описанием CAF, но оно попытается передать мою интуицию о том, чем он является, поскольку я нигде больше не видел хорошего описания действительно , чтобы я мог бы использовать его. чистое «авторитетное» описание в Вики-комментариях GHC гласит:
Постоянные аппликативные формы, или сокращенно CAF, являются значениями верхнего уровня, определенными в программе.По сути, это объекты, которые динамически не выделяются во время выполнения, а вместо этого являются частью статических данных программы.
Это хорошее начало.Чистые, функциональные, ленивые языки могут рассматриваться в некотором смысле как машина сокращения графов.Когда вы в первый раз запрашиваете значение узла, которое вызывает его оценку, что, в свою очередь, может требовать значений подузлов и т. Д. Когда один узел оценивается, результирующее значение остается неизменным (хотя оно не имеет придерживаться - так как это чистый язык, мы всегда можем поддерживать подузлы в реальном времени и пересчитывать их без семантического эффекта).CAF действительно просто ценность.Но, в контексте, особый вид значения - тот, который может определить компилятор, имеет значение, полностью зависящее от его подузлов.То есть:
foo x = ...
where thisIsACaf = [1..10::Int]
thisIsNotACaf = [1..x::Int]
thisIsAlsoNotACaf :: Num a => [a]
thisIsAlsoNotACaf = [1..10] -- oops, polymorphic! the "num" dictionary is implicitly a parameter.
thisCouldBeACaf = const [1..10::Int] x -- requires a sufficiently smart compiler
thisAlsoCouldBeACaf _ = [1..10::Int] -- also requires a sufficiently smart compiler
Так почему же нас волнует, что все это CAF?В основном потому, что иногда мы действительно не хотим пересчитывать что-либо (например, памятную записку!) И поэтому хотим убедиться, что это правильно передано.В других случаях мы действительно делаем хотим что-то пересчитать (например, огромный скучный, легко генерируемый список - такой как натуральные объекты - по которому мы просто прогуливаемся) и не оставлять его в памяти навсегда.Комбинация именования вещей и связывания их с помощью let или записи в строке и т. Д. Обычно позволяет нам определять такие вещи естественным, интуитивно понятным способом.Иногда, однако, компилятор оказывается умнее или тупее, чем мы ожидаем, и то, что мы считаем нужным вычислять только один раз, всегда пересчитывается или что-то, за что мы не хотим держаться, выдается за CAF.Затем нам нужно продумать вещи более тщательно.Посмотрите это обсуждение, чтобы получить представление о некоторых хитростях: Хороший способ избежать «обмена»?
[Между прочим, я не чувствую этого, но любой, кто хочет, может свободно взять столько ответа, сколько захочет, и попытаться интегрировать его с существующими страницами Haskell Wiki и улучшить / обновить их]