Практика 1. Программирование в λ-исчислении

осень 2026

Андрей Стоян, Илья Колегов

Летучка 1

Летучка 1 · 7 минут · Ваше ФИ: ____________________

  1. Запишите с минимумом скобок: \ ((\lambda x.\,(\lambda y.\,(x\ y)))\ a)\ b\ \equiv ______________________
  2. В терме \lambda_1 x.\,(\lambda_2 x.\,\lambda_3 y.\,x)\ (x\ y) для каждого вхождения переменной слева направо напишите номер её связывателя или «св», если она свободна: \quad x ___ \quad x ___ \quad y ___
  3. Поставьте =_\alpha или \neq_\alpha: \quad \lambda x\,y.\,x\ y ___ \lambda a\,b.\,a\ b \quad \lambda x.\,y\ x ___ \lambda z.\,y\ z \quad \lambda x.\,x\ y ___ \lambda y.\,y\ x
  4. Сделайте один β-шаг: \ (\lambda x.\,x\ x)\ (\lambda y.\,y)\ \rightsquigarrow ____________________

Метаинформация о курсе

Зачем этот курс

  • Мы возьмём простейший язык — \lambda-исчисление — и построим программирование заново
  • ⇒ Вы научитесь описывать намерение так, чтобы его проверяла машина
  • ⇒ Научитесь смотреть на каждый новый язык как на набор знакомых концепций
  • ⇒ И перестанете считать языковые инструменты магией — потому что соберёте их сами
  • NB Сегодня за час мы соберём из одних только функций: пары, логические значения и числа

Как устроена неделя

  • Δ Лекция — новая тема
  • Δ Практика
    • пишем летучку, она отрывается и сдаётся
    • мы разговариваем и решаем задачки, как и на зачётных сессиях
    • присутствуйте, используйте это время максимально, не откладывая на потом
    • ноутбуками не пользуемся
    • слайды и материалы будут — имеет смысл конспектировать только свои мысли
  • Δ Домашка
    • выдаётся каждую неделю на неделю
    • базовое закрепление и подготовка к зачётным сессиям
    • рассчитана на решения “вручную”
    • если вы всё поняли, решается очень быстро, в действительности медленнее — это нормально
  • Δ Ревью
    • после дедлайна бот рассылает решение случайно между одногруппниками
    • помощь другому — хороший способ научиться самому

Зачёт по практике

  • Летучки - нужно сдать 12 из 14, правильность не учитывается
  • Домашки - нужно сдать 12 из 14 на минимальный порог
  • Очные сессии
    • нужно сдать 3
    • 20 минут решаем задачку маркером на доске и обсуждаем
  • Старайтесь не терять темп, но будет достаточно возможностей дозакрыть долги
  • Зачёт по практике необходим, чтобы получить допуск на экзамен
  • Оценка за курс формируется на экзамене

Обозначения на слайдах

  • Δ определение
  • e.g. пример
  • ⇒ следствие, вывод
  • ✎ — задание
  • ⚙ как это выглядит на практике
  • i для общего кругозора
  • NB обратите внимание
  • ⚡ — разбор

λ-исчисление

Что значит «задать язык»

  • Δ Синтаксис — как записывать программы на этом языке
  • Δ Семантика — как исполнять программы на этом языке

Синтаксис

Формальный синтаксис термов

\Lambda ::= V \ \mid\ (\Lambda\ \Lambda) \ \mid\ (\lambda V.\,\Lambda)

Переменная x

x

Аппликация (M\ N)

@
M
N

Абстракция (\lambda x.\,M)

λx
M
  • Определение индуктивное, поэтому каждый терм — дерево
  • Скобки нужны только строковой записи, в дереве их нет

Дерево абстрактного синтаксиса (AST)

Терм записан со всеми скобками: (((\lambda x.\,(\lambda y.\,(x\ y)))\ a)\ b). Нарисуйте его дерево и запишите терм с минимумом скобок. Отметьте на дереве, какая \lambda связывает каждую переменную. Есть ли здесь свободные переменные?

Минимальная запись (\lambda x\,y.\,x\ y)\ a\ b; x связана внешней \lambda, y — внутренней, а a и b свободны.

@
@
b
λx
a
λy
@
x
y
свободна
свободна

Строковая запись и дерево

Каждая пара скобок в записи — один узел дерева. Внешние скобки — аппликация: слева функция, справа аргумент.

(((λx. (λy. (x y))) a) b)

@
M
N

Строковая запись и дерево

Снова аппликация: функция (\lambda x.\,(\lambda y.\,(x\ y))) и её аргумент a.

(((λx. (λy. (x y))) a) b)

@
@
b
λ…
a

Строковая запись и дерево

Скобки вокруг \lambda x — узел абстракции. У него один ребёнок: тело (\lambda y.\,(x\ y)).

(((λx. (λy. (x y))) a) b)

@
@
b
λx
a
λ…

Строковая запись и дерево

Ещё одна абстракция: \lambda y и её тело (x\ y).

(((λx. (λy. (x y))) a) b)

@
@
b
λx
a
λy
x y

Строковая запись и дерево

Последняя пара скобок — аппликация переменной x к переменной y. Дерево готово.

(((λx. (λy. (x y))) a) b)

@
@
b
λx
a
λy
@
x
y

Строковая запись и дерево

  • Можем убрать скобки, постановка которых не влияет на форму дерева
  • Стрелки показывают, какая \lambda связывает каждую переменную; у a и b связывателя в терме нет

(λx y. x y) a b

@
@
b
λx
a
λy
@
x
y
свободна
свободна

Семантика

Шаг β-редукции

Программа — это терм. Где в нём «вызов функции», который можно выполнить?

(λx y. x y) a b

@
@
b
λx
a
λy
@
x
y

Шаг β-редукции

  • Δ Редекс — аппликация, слева от которой стоит абстракция. На дереве это узел @, у которого левый ребёнок — \lambda

(λx y. x y) a b

@
@
b
λx
a
λy
@
x
y

Шаг β-редукции

Аргумент a занял место x, узлы @ и \lambda x исчезли.

(λy. a y) b

@
λy
b
@
a
y

Шаг β-редукции

Второй шаг — так же. Редексов не осталось, это нормальная форма.

a b

@
a
b

Запишем ту же редукцию строками

Запишите редукцию (\lambda x\,y.\,x\ y)\ a\ b цепочкой преобразований.

  1. (\lambda x\,y.\,x\ y)\ a\ b
  2. \rightsquigarrow_\beta\ (\lambda y.\,a\ y)\ b
  3. \rightsquigarrow_\beta\ a\ b

Случайный захват

Тот же терм, только аргумент теперь y. Сделайте β-шаг в уме — что получилось?

(λx y. x y) y b

@
@
b
λx
y
λy
@
x
y
свободна

Случайный захват

Пересадили «в лоб» — получили (\lambda y.\,y\ y)\ b. Но стрелка от нового y перекинулась на \lambda y: переменную захватили.

(λy. y y) b

@
λy
b
@
y
y
у переменной сменился связыватель

Случайный захват

Откатываемся. Сначала переименуем связанную переменную — терм от этого не меняется, он α-эквивалентен.

(λx y′. x y′) y b

@
@
b
λx
y
λy′
@
x
y′
свободна

Случайный захват

Теперь оба шага проходят спокойно, и свободная y остаётся свободной.

y b

@
y
b
свободна

Подстановка

\begin{alignedat}{3} &[x \mapsto N]\ x &&\equiv N \\ &[x \mapsto N]\ y &&\equiv y \\ &[x \mapsto N]\ (P\ Q) &&\equiv ([x \mapsto N]\ P)\ ([x \mapsto N]\ Q) \\ &[x \mapsto N]\ \lambda x.\,P &&\equiv \lambda x.\,P \\ &[x \mapsto N]\ \lambda y.\,P &&\equiv \lambda y.\,[x \mapsto N]\ P &&\quad y \notin FV(N) \\ &[x \mapsto N]\ \lambda y.\,P &&\equiv \lambda y'.\,[x \mapsto N]\ ([y \mapsto y']\ P) &&\quad y \in FV(N) \end{alignedat}

Выполните подстановки:

(а) [y \mapsto x]\ x\ (\lambda y.\,y\ z) \quad (б) [z \mapsto x]\ x\ (\lambda y.\,y\ z) \quad (в) [z \mapsto x\ y]\ x\ (\lambda y.\,y\ z)

(а) x\ (\lambda y.\,y\ z) — под \lambda y менять нечего \quad (б) x\ (\lambda y.\,y\ x) — проход под связыватель \quad (в) x\ (\lambda y'.\,y'\ (x\ y)) — правило 6

Когда два терма — одно и то же

Эквивалентны ли термы, и если да, то в каком смысле — синтаксически, α, β или η?

(а) x и x \quad (б) x и y \quad (в) \lambda x.\,x и \lambda y.\,y \quad (г) \lambda x.\,x и (\lambda y.\,y)\ (\lambda x.\,x) \quad (д) \lambda x\,y.\,x\ y и \lambda x.\,x

(а) это один и тот же терм, равенство синтаксическое \quad (б) не эквивалентны \quad (в) α: отличаются только именем связанной переменной \quad (г) β: правый терм редуцируется в левый \quad (д) η: \lambda y.\,x\ y и x ведут себя одинаково на любом аргументе

Кодирование данных

Пары

Записи, кортежи, пары

Одна сущность часто описывается несколькими свойствами: котик — это имя и мурлычность.

class Cat:
   def __init__(self, name, murlycity):
      self.name = name
      self.murlycity = murlycity

Чего мы хотим от такой структуры данных:

  • Δ Конструктор создаёт новое значение: barsik = Cat("barsik", 4)
  • Δ Элиминаторы достают информацию из значения: barsik.murlycity
  • NB Мутаторы (barsik.murlycity += 3) не используем: вместо изменения — новое значение Cat(barsik.name, barsik.murlycity + 3)

Спецификация пары

API пары — терм-конструктор \mathbf{pair}, элиминаторы \mathbf{fst} и \mathbf{snd}

Законы пар

  1. \mathbf{fst}\ (\mathbf{pair}\ N\ M) =_\beta N
  2. \mathbf{snd}\ (\mathbf{pair}\ N\ M) =_\beta M

Запишите \mathbf{swap}, меняющий компоненты пары местами, через \mathbf{pair}, \mathbf{fst} и \mathbf{snd}. Проверьте по законам: \mathbf{fst}\ (\mathbf{swap}\ (\mathbf{pair}\ N\ M)) =_\beta M.

\begin{aligned} &\mathbf{swap} := \lambda p.\,\mathbf{pair}\ (\mathbf{snd}\ p)\ (\mathbf{fst}\ p) \\ &\mathbf{fst}\ (\mathbf{swap}\ (\mathbf{pair}\ N\ M)) =_\beta \mathbf{fst}\ (\mathbf{pair}\ (\mathbf{snd}\ (\mathbf{pair}\ N\ M))\ \ldots) =_\beta \mathbf{snd}\ (\mathbf{pair}\ N\ M) =_\beta M \end{aligned}

Пары в \lambda-исчислении

  • В \lambda-исчислении нет ничего, кроме функций. Где хранить два значения?

Конструктор принимает два значения и функцию, которой отдаёт оба:

  • \mathbf{pair} := \lambda x\,y.\,\lambda f.\,f\ x\ y — любой терм вида \lambda f.\,f\ N\ M можно считать парой

Элиминаторы:

  • \mathbf{fst} := \lambda p.\,p\ \mathbf{K}
  • \mathbf{snd} := \lambda p.\,p\ \mathbf{K}_*

Проверка законов пар

\mathbf{pair} := \lambda x\,y.\,\lambda f.\,f\ x\ y \qquad \mathbf{fst} := \lambda p.\,p\ \mathbf{K} \qquad \mathbf{snd} := \lambda p.\,p\ \mathbf{K}_*

Проверьте, что второй закон пар выполняется.

\begin{alignedat}{2} & &&\ \mathbf{snd}\ (\mathbf{pair}\ N\ M) \\ &\rightsquigarrow_{\mathbf{pair}} &&\ \mathbf{snd}\ ((\lambda x\,y.\,\lambda f.\,f\ x\ y)\ N\ M) \\ &\rightsquigarrow_\beta &&\ \mathbf{snd}\ (\lambda f.\,f\ N\ M) \\ &\rightsquigarrow_{\mathbf{snd}} &&\ (\lambda p.\,p\ \mathbf{K}_*)\ (\lambda f.\,f\ N\ M) \\ &\rightsquigarrow_\beta &&\ (\lambda f.\,f\ N\ M)\ \mathbf{K}_* \\ &\rightsquigarrow_\beta &&\ \mathbf{K}_*\ N\ M \twoheadrightarrow M \end{alignedat}

Итого \mathbf{snd}\ (\mathbf{pair}\ N\ M) \twoheadrightarrow M — дальше пользуемся этим как правилом «большого шага».

Логические величины

Спецификация логических величин

API логических величин — конструкторы-константы \mathbf{true} и \mathbf{false}, элиминатор \mathbf{if}

Сформулируйте законы логических величин по аналогии с законами пар.

  1. \mathbf{if}\ \mathbf{true}\ T\ E =_\beta T
  2. \mathbf{if}\ \mathbf{false}\ T\ E =_\beta E

Логические величины в \lambda-исчислении

Конструкторы принимают две ветки и выбирают из них свою:

  • \mathbf{true} := \lambda t\,e.\,t — это \mathbf{K}
  • \mathbf{false} := \lambda t\,e.\,e — это \mathbf{K}_*

Элиминатор — выбор уже сделан значением, осталось применить:

  • \mathbf{if} := \lambda b\,t\,e.\,b\ t\ e, или просто \mathbf{if} := \mathbf{I}

Операции над логическими величинами: отрицание

Закодируйте \mathbf{not} через \mathbf{if} по таблице истинности:

b \neg b
true false
false true

\mathbf{not} := \lambda b.\,\mathbf{if}\ b\ \mathbf{false}\ \mathbf{true}

А \mathbf{and}? Подсказка: если a истинно, ответ зависит только от b.

\mathbf{and} := \lambda a\,b.\,\mathbf{if}\ a\ b\ \mathbf{false}

Натуральные числа

Натуральные числа \mathbb{N} := \{0, 1, 2, 3, \cdots\}

API натуральных чисел

  • Первая аксиома Пеано: ноль является натуральным числом (\mathbf{0})
  • Вторая аксиома Пеано: число, следующее за натуральным, тоже натуральное (\mathbf{suc}\ n)
  • Элиминатор: воспользоваться натуральным числом — что-то сделать n раз, \mathbf{natElim}

Законы для натуральных чисел

  1. \mathbf{natElim}\ \mathbf{0}\ F\ Z =_\beta Z
  2. \mathbf{natElim}\ (\mathbf{suc}\ N)\ F\ Z =_\beta F\ (\mathbf{natElim}\ N\ F\ Z)

Законы natElim в действии

Число \mathbf{3} — это \mathbf{suc}\ (\mathbf{suc}\ (\mathbf{suc}\ \mathbf{0})). Посчитаем \mathbf{natElim}\ \mathbf{3}\ F\ Z одними законами, не зная, как числа устроены.

natElim 3 F Z

цепочка

\mathbf{natElim}\ \mathbf{3}\ F\ Z

Законы natElim в действии

\mathbf{3} — это \mathbf{suc}\ \mathbf{2}: второй закон снимает один \mathbf{suc} и применяет F один раз.

F (natElim 2 F Z)

цепочка

\mathbf{natElim}\ \mathbf{3}\ F\ Z

\rightsquigarrow_{\mathbf{3}}\ \mathbf{natElim}\ (\mathbf{suc}\ \mathbf{2})\ F\ Z

=_\beta\ F\ (\mathbf{natElim}\ \mathbf{2}\ F\ Z) — закон 2

Законы natElim в действии

Ещё раз: \mathbf{2} — это \mathbf{suc}\ \mathbf{1}.

F (F (natElim 1 F Z))

цепочка

\mathbf{natElim}\ \mathbf{3}\ F\ Z

\rightsquigarrow_{\mathbf{3}}\ \mathbf{natElim}\ (\mathbf{suc}\ \mathbf{2})\ F\ Z

=_\beta\ F\ (\mathbf{natElim}\ \mathbf{2}\ F\ Z) — закон 2

\rightsquigarrow_{\mathbf{2}}\ F\ (\mathbf{natElim}\ (\mathbf{suc}\ \mathbf{1})\ F\ Z)

=_\beta\ F\ (F\ (\mathbf{natElim}\ \mathbf{1}\ F\ Z)) — закон 2

Законы natElim в действии

И ещё: \mathbf{1} — это \mathbf{suc}\ \mathbf{0}.

F (F (F (natElim 0 F Z)))

цепочка

\mathbf{natElim}\ \mathbf{3}\ F\ Z

\rightsquigarrow_{\mathbf{3}}\ \mathbf{natElim}\ (\mathbf{suc}\ \mathbf{2})\ F\ Z

=_\beta\ F\ (\mathbf{natElim}\ \mathbf{2}\ F\ Z) — закон 2

\rightsquigarrow_{\mathbf{2}}\ F\ (\mathbf{natElim}\ (\mathbf{suc}\ \mathbf{1})\ F\ Z)

=_\beta\ F\ (F\ (\mathbf{natElim}\ \mathbf{1}\ F\ Z)) — закон 2

\rightsquigarrow_{\mathbf{1}}\ F\ (F\ (\mathbf{natElim}\ (\mathbf{suc}\ \mathbf{0})\ F\ Z))

=_\beta\ F\ (F\ (F\ (\mathbf{natElim}\ \mathbf{0}\ F\ Z))) — закон 2

Законы natElim в действии

Дошли до \mathbf{0} — первый закон возвращает Z. F применили столько раз, сколько \mathbf{suc} в числе.

F (F (FZ))

цепочка

\mathbf{natElim}\ \mathbf{3}\ F\ Z

\rightsquigarrow_{\mathbf{3}}\ \mathbf{natElim}\ (\mathbf{suc}\ \mathbf{2})\ F\ Z

=_\beta\ F\ (\mathbf{natElim}\ \mathbf{2}\ F\ Z) — закон 2

\rightsquigarrow_{\mathbf{2}}\ F\ (\mathbf{natElim}\ (\mathbf{suc}\ \mathbf{1})\ F\ Z)

=_\beta\ F\ (F\ (\mathbf{natElim}\ \mathbf{1}\ F\ Z)) — закон 2

\rightsquigarrow_{\mathbf{1}}\ F\ (F\ (\mathbf{natElim}\ (\mathbf{suc}\ \mathbf{0})\ F\ Z))

=_\beta\ F\ (F\ (F\ (\mathbf{natElim}\ \mathbf{0}\ F\ Z))) — закон 2

=_\beta\ F\ (F\ (F\ Z)) — закон 1

Операции над числами Чёрча: чётность

Закодируйте \mathbf{isEven} одними законами. Рецепт:

  1. базовые случаи — \mathbf{isEven}\ \mathbf{0} =_\beta \mathbf{true}, \mathbf{isEven}\ \mathbf{1} =_\beta \mathbf{false}, \mathbf{isEven}\ \mathbf{2} =_\beta \mathbf{true}, …
  2. как воспользоваться числом — \mathbf{natElim}\ \mathbf{3}\ F\ Z \twoheadrightarrow F\ (F\ (F\ Z))
  3. подобрать F и Z

Состояние — пара «ответ сейчас, ответ на следующем шаге»: \mathbf{start} := \mathbf{pair}\ \mathbf{true}\ \mathbf{false}, шаг — \mathbf{swap} из разбора пар, \mathbf{isEven} := \lambda n.\,\mathbf{fst}\ (\mathbf{natElim}\ n\ \mathbf{swap}\ \mathbf{start})

Состояние — одно логическое значение: \mathbf{isEven} := \lambda n.\,\mathbf{natElim}\ n\ \mathbf{not}\ \mathbf{true}

Числа Чёрча

Число знает, сколько раз применить шаг s к начальному значению z:

\mathbf{0} := \lambda s\,z.\,z \qquad \mathbf{1} := \lambda s\,z.\,s\ z \qquad \mathbf{2} := \lambda s\,z.\,s\ (s\ z) \qquad \ldots

Элиминатор — снова применить значение к аргументам: \mathbf{natElim} := \lambda n\,s\,z.\,n\ s\ z, то есть \mathbf{I}.

Числа по Пеано — те же самые: \mathbf{suc}\ (\mathbf{suc}\ (\mathbf{suc}\ \mathbf{0})) \twoheadrightarrow \lambda s\,z.\,s\ (s\ (s\ z)), и законы выполняются.

⌜2⌝
z
s
s
⌜3⌝
z
s
s
s

Как работают числа Чёрча

Число — конвейер; \mathbf{natElim} ставит в него трансформации и вход и запускает.

\mathbf{3} := \lambda s\,z.\,s\ (s\ (s\ z))
z
s
s
s

Как работают числа Чёрча

Число — конвейер; \mathbf{natElim} ставит в него трансформации и вход и запускает.

\mathbf{3} := \lambda s\,z.\,s\ (s\ (s\ z))
z
s
s
s
\mathbf{natElim}\ \mathbf{3}\ \mathbf{not}\ \mathbf{true} \twoheadrightarrow \mathbf{3}\ \mathbf{not}\ \mathbf{true}
true
not
not
not

Как работают числа Чёрча

Число — конвейер; \mathbf{natElim} ставит в него трансформации и вход и запускает.

\mathbf{3} := \lambda s\,z.\,s\ (s\ (s\ z))
z
s
s
s
\mathbf{natElim}\ \mathbf{3}\ \mathbf{not}\ \mathbf{true} \twoheadrightarrow \mathbf{3}\ \mathbf{not}\ \mathbf{true} \twoheadrightarrow \mathbf{not}\ (\mathbf{not}\ \mathbf{false})
false
not
not

Как работают числа Чёрча

Число — конвейер; \mathbf{natElim} ставит в него трансформации и вход и запускает.

\mathbf{3} := \lambda s\,z.\,s\ (s\ (s\ z))
z
s
s
s
\mathbf{natElim}\ \mathbf{3}\ \mathbf{not}\ \mathbf{true} \twoheadrightarrow \mathbf{3}\ \mathbf{not}\ \mathbf{true} \twoheadrightarrow \mathbf{not}\ (\mathbf{not}\ \mathbf{false}) \twoheadrightarrow \mathbf{not}\ \mathbf{true}
true
not

Как работают числа Чёрча

Число — конвейер; \mathbf{natElim} ставит в него трансформации и вход и запускает.

Трансформаций не осталось: \mathbf{natElim}\ \mathbf{3}\ \mathbf{not}\ \mathbf{true} \twoheadrightarrow \mathbf{false}. Какая семантика у терма \mathbf{natElim}\ n\ \mathbf{not}\ \mathbf{true}?

Чётность: это \mathbf{isEven} с прошлого задания, \lambda n.\,\mathbf{natElim}\ n\ \mathbf{not}\ \mathbf{true}

\mathbf{3} := \lambda s\,z.\,s\ (s\ (s\ z))
z
s
s
s
\mathbf{natElim}\ \mathbf{3}\ \mathbf{not}\ \mathbf{true} \twoheadrightarrow \mathbf{3}\ \mathbf{not}\ \mathbf{true} \twoheadrightarrow \mathbf{not}\ (\mathbf{not}\ \mathbf{false}) \twoheadrightarrow \mathbf{not}\ \mathbf{true} \twoheadrightarrow \mathbf{false}
false

Следующее число

Запишите \mathbf{suc}: результат должен применять s на один раз больше, чем n. Подсказка: воспользуйтесь числом n через \mathbf{natElim}.

⌜2⌝
z
s
s

Следующее число

Запишите \mathbf{suc}: результат должен применять s на один раз больше, чем n. Подсказка: воспользуйтесь числом n через \mathbf{natElim}.

Новое s — снаружи: \mathbf{suc} := \lambda n.\,\lambda s\,z.\,s\ (\mathbf{natElim}\ n\ s\ z).

Можно и изнутри: \lambda n.\,\lambda s\,z.\,\mathbf{natElim}\ n\ s\ (s\ z) — цепочка та же.

⌜3⌝
z
s
s
s

Операции над числами Чёрча: сложение

Закодируйте сложение.

⌜2⌝
z
s
s
⌜3⌝
z
s
s
s

Операции над числами Чёрча: сложение

Закодируйте сложение.

\mathbf{plus} := \lambda n\,m.\,\lambda s\,z.\,\mathbf{natElim}\ n\ s\ (\mathbf{natElim}\ m\ s\ z).

Или n раз увеличить m на единицу: \mathbf{plus} := \lambda n\,m.\,\mathbf{natElim}\ n\ \mathbf{suc}\ m

z
s
s
s
s
s
⌜3⌝, потом ⌜2⌝ = ⌜5⌝

Что вы теперь умеете

с лекции \mathbf{I}\quad \mathbf{K}\quad \mathbf{K}_*\quad \mathbf{S}\quad \mathbf{B}
пары \mathbf{pair}\quad \mathbf{fst}\quad \mathbf{snd}
логика \mathbf{true}\quad \mathbf{false}\quad \mathbf{if}\quad \mathbf{not}
числа \mathbf{0}\quad \mathbf{suc}\quad \mathbf{plus}
  • Мы задали простейший язык программирования — описали синтаксис и семантику \lambda-исчисления
    • программы (термы) — деревья правильного вида
    • вычисление — переписывание деревьев
  • Мы спроектировали первые структуры данных в \lambda-исчислении
    • начинали с API — набор конструкторов и элиминаторов
    • записывали законы, определяющие поведение конструкторов и элиминаторов
    • конструировали термы, удовлетворяющие законам

Закодируйте аналогичным образом структуры данных в вашем любимом языке программирования.

Материалы

Каррирование

Эквивалентен ли код?

\mathbf{K}_* := \lambda x\,y.\,y

def k_star(x, y):
    return y

Нет. \mathbf{K}_*\ q \rightsquigarrow \lambda y.\,y — получилась функция, а k_star(q) — ошибка «missing 1 required positional argument». Правильное прочтение: \lambda x\,y.\,y \equiv \lambda x.\,\lambda y.\,y, то есть функция, которая игнорирует аргумент и возвращает другую функцию:

def k_star(x):
    def helper(y):
        return y
    return helper
  • Δ Каррирование превращает функцию многих аргументов в набор вложенных функций, каждая из которых берёт часть аргументов. В ООП методы каррированы: часть «аргументов» приходит через конструктор

Замыкания и функции высших порядков

Закодируем \mathbf{K} := \lambda x.\,\lambda y.\,x:

def k(x):
    def helper(y):
        return x      # x — из окружения
    return helper
  • Δ Замыкание — функция вместе с окружением, то есть состоянием, к которому её код имеет доступ. Так прикладные языки и представляют λ-функции
auto screen = Screen::create(...);
clearButton.onClick([screen]() {
    screen.textField = "";
});

\mathbf{B} := \lambda f.\,\lambda g.\,\lambda x.\,f\ (g\ x) принимает функции:

def compose(f, g):
    def helper(x):
        return f(g(x))
    return helper
  • Δ Функция высшего порядка принимает другие функции в качестве аргументов
  • ⚙ Каррировать по всем аргументам на практике не всегда удобно: compose(f, g) берёт сразу два

Harvard CS153, Lec 12: Functions

Законы и algebra driven design

Закодировав пару, мы получили абстракцию по данным:

  • Это уже не два значения, а доменная сущность
  • Реализацию можно выбрать любую, лишь бы выполнялись законы, — и дальше забыть, как она устроена
  • Её можно в любой момент подменить, например на более эффективную, и пользовательский код не сломается
  • Соответствие законам можно проверять автоматически

Прочитать: Sandy Maguire, Algebra-Driven Design; посмотреть: John Hughes — How to specify it! (Lambda Days 2020).

Идея кодирования по Чёрчу

  • Пусть функция не возвращает результат, а принимает способ его сразу обработать: «штуку я не дам, скажи, зачем она тебе, — я всё сделаю»
  • Вместо того чтобы нести объект туда, где его разберут, мы приносим разбор туда, где объект создаётся: вместо (X) -> R получается (X, (R) -> a) -> a
  • Так избавляются от аллокаций и промежуточных структур: десериализатор вместо std::variant<Msg1, Msg2> сразу вызывает Impl::processMsg1(...) или Impl::processMsg2(...)

Это известно как tagless final, кодирование Чёрча — Бёма — Берардуччи и CPS-преобразование; всё это ещё встретится в курсе.

Объектно-ориентированная запись пар по Чёрчу

На самом деле, приоритезировать элиминаторы - более характерно для ОО стиля.

interface Pair<A, B> {
    fun <R> fold(f: (A, B) -> R): R
}

fun <A, B> pair(x: A, y: B): Pair<A, B> = object : Pair<A, B> {
    override fun <R> fold(f: (A, B) -> R): R = f(x, y)
}

fun <A, B> fst(p: Pair<A, B>): A = p.fold { x, _ -> x }

fun <A, B> snd(p: Pair<A, B>): B = p.fold { _, y -> y }

object : Interface — это конструкция для определения анонимного класса-наследника. Аналогичным образом можно сделать через именованные классы.

Объектно-ориентированная запись логических величин

interface Bool {
    fun <R> myif(t: R, e: R): R
}

val tru = object : Bool {
    override fun <R> myif(t: R, e: R): R = t
}

val fls = object : Bool {
    override fun <R> myif(t: R, e: R): R = e
}

fun not(b: Bool): Bool = b.myif(fls, tru)

Объектно-ориентированная запись натуральных чисел

interface Nat {
    fun <T> elim(z: T, s: (T) -> T): T
}

val zero = object : Nat {
    override fun <T> elim(z: T, s: (T) -> T): T = z
}

fun suc(n: Nat): Nat = object : Nat {
    override fun <T> elim(z: T, s: (T) -> T): T =
        s(n.elim(z, s))
}

fun plus(n: Nat, m: Nat): Nat = n.elim(m, ::suc)

Что посмотреть в транспорте

Серьёзные материалы