В чистом λ-исчислении нет ни чисел, ни булевых значений, ни структур — только функции. Кодирование Чёрча показывает, что ничего другого и не нужно: данные — это их собственные операции обработки.
Начнём с булевых значений. И заметьте, как тут всё практично привязано. Не существует правды и лжи самих по себе: эти понятия имеют смысл только тогда, когда предполагают стратегии действия. В противном случае смысла в существовании правды и лжи нет. Прочувствуйте формулы.
Здесь и — это просто два аргумента (мнемоника: ветка «true» и ветка «false»): возвращает первый, а — второй. То есть само булево значение — это уже выбор одного из двух, готовый .
Здесь почти ничего не делает сам: он берёт булево значение и отдаёт ему две ветки и , а уже выбирает нужную. устроен так же просто: он даёт булевому значению ветки в обратном порядке, поэтому правда выбирает ложь, а ложь — правду.
В первое булево значение решает, что вернуть: если истинно, результат зависит от , а если ложно, сразу возвращается ложь. В наоборот: если истинно, можно сразу вернуть правду, а если ложно, приходится смотреть на .
Подставим истину (, ) и произведём редукции:
Теперь поговорим о парах: это очень важный тип, который широко используется в Haskell. Пара — функция, хранящая два значения и ждущая «получателя»:
передаёт паре , а — , и та отдаёт нужную компоненту:
Заметьте, как интересно: функции и принимают аргумент-пару и передают ей управление — дальше она сама решает, что возвращать. Это, так сказать, функции-декораторы.
Теперь самое насущное — натуральные числа. Число — это «применить функцию раз»:
Важно: и здесь произвольные — их подставляет тот, кто пользуется числом. Нумерал фиксирует только «сколько раз применить», а не «что применять».
Скормим нумералу функцию и аргумент — и он применит ровно столько раз, сколько задаёт число:
Теперь научимся считать — вся арифметика вырастает из одного приёма «применить нужное число раз».
Начнём с прибавления единицы: навешивает на число ещё одно применение :
Сложение продолжает эту идею — применяет сначала раз, а потом ещё :
Умножение — это уже повторное сложение:
Возведение в степень — повторное умножение, и в записи оно совсем короткое:
В аргументы просто меняются местами: — это , применённое как функция раз, что и даёт .
Вычитание неожиданно трудно: строится через пары — прогоняем по числу пару и берём первую компоненту:
По распространённой легенде Стивен Клини придумал функцию-предшественник (, predecessor) — ключ к вычитанию нумералов Чёрча — прямо в кресле у стоматолога.
На держится всё «вычитательное»: само вычитание — это многократный , а деление — многократное вычитание. Отдельного λ-определения для деления не приводим: новой идеи там нет, тот же в цикле, только запись громоздкая.
Проверим на общем нумерале . Обозначим шаг — он сдвигает пару: роняет первую компоненту и наращивает вторую:
Подставляем в определение — это значит применить ровно раз к стартовой паре :
Пары бегут по цепочке — вторая компонента считает шаги, первая отстаёт на единицу:
Осталось взять первую компоненту:
У цепочка не двигается, поэтому — предшественником нуля считаем нуль.
скармливает числу функцию, которая на каждом шаге отвечает , и старт : у нуля шагов нет — остаётся , а у любого другого числа хотя бы один шаг даёт .
Список — его собственная правая свёртка (fold-кодирование):
Тот же приём в Haskell — тип : список полностью определяется тем, как он сворачивается. Нумерал Чёрча — «список из одинаковых применений».
-- список строится конструктором (:) (cons) и завершается [] (nil):
[1, 2, 3] == 1 : (2 : (3 : [])) -- True
-- Чёрч-кодирование — та же цепочка, только (:) и [] вынесены в параметры;
-- в Haskell это ровно foldr:
foldr (:) [] [1, 2, 3] -- 1 : (2 : (3 : [])) = [1,2,3] (собрали список обратно)
foldr (+) 0 [1, 2, 3] -- 1 + (2 + (3 + 0)) = 6 (свернули в сумму)Ну ведь есть же нормальное программирование, в котором есть и числа, и даже с плавающей точкой. Зачем всё это городить? Я хотел декларативного программирования, где всё просто, а тут... На самом деле ответ простой: ребята, если вы не любите цирк, вам там нечего делать, не приходите, но если вы любите цирк...