Переливание из пустого в порожнее продолжается, и сейчас поговорим о β-редукции. Именно не о конверсии, так как экспансии тут быть не может: есть только редукция. Вообще вся операционная семантика λ-исчисления — одно правило переписывания, не более чем.
Вызов функции в любом языке — частный случай β-редукции.
Терм вида называется редексом. Редукция может применяться к любому редексу в любом месте терма. Обозначение означает один шаг β-редукции, — ноль и более шагов, а — эквивалентность, порождённая редукцией. Нормальная форма, или НФ, — это терм без редексов: вычислять больше нечего. Результатом работы программы как раз и должна быть нормальная форма.
Если запретить редукцию под знаком λ — не заглядывать в тело абстракции, — получается более слабое понятие: слабая нормальная форма. В ней сожжены все редексы, кроме спрятанных под λ. Скажем, уже в слабой нормальной форме, хотя обычной НФ ещё не достиг. Именно до такой формы (точнее, слабой головной, WHNF) и вычисляют реальные языки: тело функции не трогают, пока её не позвали — подробнее в разделе о стратегиях редукции.
Так в Haskell живут бесконечные структуры: их вычисляют до слабой головной формы, но не до конца.
nats = [1..] -- бесконечный список 1 : 2 : 3 : …
-- уже в WHNF: виден внешний конструктор (:), хвост — невычисленный thunk;
-- полной НФ нет — чтобы её достичь, пришлось бы построить весь список
take 3 nats -- [1,2,3]: берём только начало, и всё работаетПримеры β-редукций:
Но, как всегда, есть нюансы, и главный нюанс заключается в том, что далеко не все термы редуцируются до нормальной формы. Выше на Haskell я уже дал пример бесконечного списка, но тут будет не о нём. Терм редуцируется сам в себя — вычисление не завершается никогда:
Но на этом приколы не заканчиваются: бывает так, что порядок вычисления редексов влияет на то, зациклится алгоритм или нет. В ленивых языках такое сплошь и рядом, но компилятор обычно умеет сам это разруливать, если мы не навяжем ему свою волю. В общем, дальше только интереснее будет.