Практика 3. Типы в λ-исчислении

осень 2026

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

осень 2026

Летучка 3

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

  1. Расставьте все скобки: \ f\ x\ y\ z \equiv ______________
  2. Какой \lambda связывает последнее вхождение x в терме \lambda_1 x.\,\lambda_2 y.\,x\ (\lambda_3 x.\,y\ x)? Номер: ___
  3. Сделайте один β-шаг: \ (\lambda f\,x.\,f\ (f\ x))\ \mathbf{not} \rightsquigarrow __________________
  4. Как называются функции, принимающие функции в качестве аргументов? ____________________
  5. Свойство комбинатора неподвижной точки: \ \mathbf{Y}\ F =_\beta __________

Идея систем типов

Анализ типов: метки от листьев к корню

Программа — это дерево. Попробуем приписать каждой вершине метку-тип, двигаясь от листьев к корню.

if (x < 2) (x + 1) 0

if
<
+
0
x
2
x
1

Анализ типов: метки от листьев к корню

Листья. Константа знает свою метку сама; про переменную x нужно знать заранее — пусть x : \mathrm{Int}.

if (x < 2) (x + 1) 0

if
<
+
0
x
2
x
1
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}

Анализ типов: метки от листьев к корню

Правило для <: если оба поддерева помечены \mathrm{Int}, вершина получает метку \mathrm{Bool}. Правило для +: оба поддерева \mathrm{Int} — метка \mathrm{Int}.

if (x < 2) (x + 1) 0

if
<
+
0
x
2
x
1
: \mathrm{Bool}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}

Анализ типов: метки от листьев к корню

Правило для if: условие помечено \mathrm{Bool}, ветки помечены одинаково — вершина получает метку веток. Помечено всё дерево: программа «хорошая».

if (x < 2) (x + 1) 0

if
<
+
0
x
2
x
1
: \mathrm{Int}
: \mathrm{Bool}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}

Анализ типов: метки от листьев к корню

А если сравнить x с переменной b, про которую дано b : \mathrm{Bool}? Правило для < не срабатывает, метки нет, выше идти не с чем: программа отвергнута до запуска.

if (x < b) (x + 1) 0

if
<
+
0
x
b
x
1
: \ ?
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}
: \mathrm{Bool}
: \mathrm{Int}
: \mathrm{Int}

Типы — тоже деревья

Здесь f помечена \mathrm{Int}\to\mathrm{Int}, а вся программа — (\mathrm{Int}\to\mathrm{Int})\to\mathrm{Int}.

λf. f (f 1)

λf
@
f
@
f
1
: (\mathrm{Int}\to\mathrm{Int})\to\mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}\to\mathrm{Int}
: \mathrm{Int}
: \mathrm{Int}\to\mathrm{Int}
: \mathrm{Int}

Типы — тоже деревья

Стрелка — узел с двумя поддеревьями: слева тип аргумента, справа тип результата. Листья — константы \mathrm{Int}, \mathrm{Bool} и типовые переменные \alpha, \beta.

λf. f (f 1)

λf
@
f
@
f
1
: (\mathrm{Int}\to\mathrm{Int})\to\mathrm{Int}
: \mathrm{Int}
:
: \mathrm{Int}
:
: \mathrm{Int}
→
Int
Int
→
Int
Int

Типы — тоже деревья

Тип всей программы — тоже дерево: (\mathrm{Int}\to\mathrm{Int})\to\mathrm{Int}. Скобки в записи — это форма дерева: слева поддерево \mathrm{Int}\to\mathrm{Int}, программа принимает функцию.

λf. f (f 1)

λf
@
f
@
f
1
:
: \mathrm{Int}
:
: \mathrm{Int}
:
: \mathrm{Int}
→
Int
Int
→
Int
Int
→
→
Int
Int
Int

Типы — тоже деревья

Остальные метки — деревья из одного листа. Итог: анализ типов аннотирует дерево программы деревьями типов; осталось договориться, по каким правилам метка вершины получается из меток поддеревьев.

λf. f (f 1)

λf
@
f
@
f
1
:
:
:
:
:
:
→
Int
Int
→
Int
Int
→
→
Int
Int
Int
Int
Int
Int

Типы — тоже деревья

Без скобок стрелка группируется вправо: \lambda x\,y.\,x \mathrel{\mathtt{+}} y : \mathrm{Int}\to\mathrm{Int}\to\mathrm{Int} — это \mathrm{Int}\to(\mathrm{Int}\to\mathrm{Int}), функция, возвращающая функцию. У нашей программы скобки слева — другое дерево.

λf. f (f 1)

λf
@
f
@
f
1
:
:
:
:
:
:
\lambda x\,y.\,x \mathrel{\mathtt{+}} y :
\mathrm{Int}\to(\mathrm{Int}\to\mathrm{Int})
(\mathrm{Int}\to\mathrm{Int})\to\mathrm{Int}
→
Int
Int
→
Int
Int
→
→
Int
Int
Int
Int
Int
Int
→
Int
→
Int
Int

Анализ типов

  • Попытаемся приписать каждой ноде программы синтаксические метки по определённым правилам
  • Если метки таким образом приписать можно — программа «хорошая»
  • Вид таких меток и правил задаётся системой типов
  • Анализ типов — разновидность статического анализа: программу проверяем, не запуская

λ→ интуитивно

Наш язык на сегодня: \lambda_\to и константы

Синтаксис \lambda_\to — как в лекции + наше расширение

  • Типы: \quad T ::= \alpha \ \mid\ T \to T \ \mid\ \mathrm{Int} \ \mid\ \mathrm{Bool}

  • Термы: \quad M ::= x \ \mid\ M\ M \ \mid\ \lambda x.\,M \qquad по Чёрчу — \lambda x^T.\,M

    \phantom{M ::=} \mid\ n \ \mid\ M \mathrel{\mathtt{+}} M \ \mid\ M \mathrel{\mathtt{-}} M \ \mid\ M \mathrel{\mathtt{*}} M, где n \in \mathbb{N} = \{\mathtt{0}, \mathtt{1}, \mathtt{2}, \ldots\}

    \phantom{M ::=} \mid\ b \ \mid\ M \mathrel{\mathtt{<}} M \ \mid\ M \mathrel{\mathtt{==}} M \ \mid\ \mathtt{if}\ M\ M\ M, где b \in \mathbb{B} = \{\mathtt{true}, \mathtt{false}\}

Расширение — числа и були встроены в язык как новые формы терма. Это не \mathbf{plus} и \mathbf{true} из практики 1: раскрыть их в \lambda-терм нельзя; \mathtt{2} \mathrel{\mathtt{+}} \mathtt{3} \rightsquigarrow_{+} \mathtt{5} происходит за один шаг

Типизация интуитивно: конкретные типы

Припишите тип терму \lambda x.\,x \mathrel{\mathtt{+}} \mathtt{1}

Функция: ?\to\ ?. Складывать можно только числа — x : \mathrm{Int}, сумма — число. Ответ: \mathrm{Int}\to\mathrm{Int}

Припишите тип терму \lambda b.\,\mathtt{if}\ b\ \mathtt{0}\ \mathtt{1}

b — условие: b : \mathrm{Bool}; обе ветки \mathrm{Int}, значит и всё \mathtt{if} — \mathrm{Int}. Ответ: \mathrm{Bool}\to\mathrm{Int}

Припишите тип терму \mathtt{true} \mathrel{\mathtt{+}} \mathtt{1}

Типа нет: слагаемые обязаны быть числами, а \mathtt{true} : \mathrm{Bool}

Типизация интуитивно: буквы

Найдите наиболее общий тип терма \lambda x\,y.\,x

Типы аргументов в терме не записаны, а тело их никак не ограничивает — выбираем различные буквы: \alpha\to\beta\to\ ?. Результат — первый аргумент. Ответ: \alpha\to\beta\to\alpha

Найдите наиболее общий тип терма \lambda x\,y\,f.\,f\ x\ y

f в теле принимает два аргумента — x и y, на них ограничений нет: \alpha\to\beta\to(\alpha\to\beta\to\ ?)\to\ ?. Результат f ничем не ограничен — берём свежую букву. Ответ: \alpha\to\beta\to(\alpha\to\beta\to\gamma)\to\gamma

Типизация интуитивно: функции-аргументы

Припишите тип терму \lambda x^\alpha\,f^{\alpha \to \beta}.\,f\ x — здесь типы аргументов уже подписаны

Видим абстракцию — пишем стрелку \alpha\to\ ? и типизируем тело, зная x^\alpha. Тело — тоже функция: \alpha\to((\alpha\to\beta)\to\ ?). Внутри знаем x^\alpha и f^{\alpha\to\beta}: тип x совпадает с типом аргумента f, значит f\ x : \beta. Ответ: \alpha\to(\alpha\to\beta)\to\beta

Найдите наиболее общий тип терма \lambda f\,x.\,f\ (f\ x)

f применяют, значит f : \alpha\to\beta. Результат f\ x снова отдают в f, значит \beta и \alpha — один тип; x — того же типа. Ответ: (\alpha\to\alpha)\to\alpha\to\alpha

Типизация самоприменения

А какой тип у \lambda x.\,x\ x?

Никакого. x применяют — значит x : \alpha\to\beta; аргументом стоит тот же x — значит x : \alpha. Получается \alpha \equiv \alpha\to\beta: тип должен быть собственной частью, а у конечного дерева так не бывает.

Население типов интуитивно

Населите тип (\beta\to\gamma)\to(\alpha\to\beta)\to\alpha\to\gamma — то есть постройте терм такого типа

  • Вспоминаем про ассоциативность стрелки: (\beta\to\gamma)\to((\alpha\to\beta)\to\alpha\to\gamma)
  • Это явно функция, пишем абстрактор: \lambda f^{\beta\to\gamma}.\,\underbrace{\square}_{(\alpha\to\beta)\to\alpha\to\gamma}
  • Аналогично ещё две стрелки дают два аргумента: \lambda f^{\beta\to\gamma}\,g^{\alpha\to\beta}\,x^\alpha.\,\underbrace{\square}_\gamma
  • Единственный способ получить значение типа \gamma — вызвать f: \quad\lambda f^{\beta\to\gamma}\,g^{\alpha\to\beta}\,x^\alpha.\,f\ \underbrace{\square}_\beta
  • Конструируем подходящий аргумент для f типа \beta вызовом g: \quad\lambda f^{\beta\to\gamma}\,g^{\alpha\to\beta}\,x^\alpha.\,f\ (g\ \underbrace{\square}_\alpha)
  • Воспользуемся x, чтобы достроить терм нужного типа: \quad\lambda f^{\beta\to\gamma}\,g^{\alpha\to\beta}\,x^\alpha.\,f\ (g\ x)

Буквы и константы

(а) \lambda x.\,x : \alpha\to\alpha. Какие из типов тоже подходят этому терму: \ \mathrm{Int}\to\mathrm{Int}, \quad (\beta\to\gamma)\to(\beta\to\gamma), \quad \mathrm{Int}\to\mathrm{Bool}, \quad \alpha\to\beta?

(б) \lambda x.\,x \mathrel{\mathtt{+}} \mathtt{1} : \mathrm{Int}\to\mathrm{Int}. А тип \alpha\to\alpha ему подходит?

(в) \lambda x\,y.\,x имеет типы \alpha\to\alpha\to\alpha и \alpha\to\beta\to\alpha. Который из них главнее?

  • (а) Первые два. Вместо буквы подставляется любой тип, но один и тот же во всех её вхождениях: \alpha\to\alpha — не «из любого в любой»
  • (б) Нет. Константный тип \mathrm{Int} конкретен, у него свои значения 0, 1, 2, \ldots: букву превратить в \mathrm{Int} можно, \mathrm{Int} в букву — нет
  • (в) Второй: первый получается из него подстановкой \beta := \alpha, обратно — нет. Наиболее общий тип — тот, из которого все остальные получаются подстановкой вместо букв

λ→ формально

Контекст типизации

  • В программах используются введённые ранее имена
  • Для типизации нужно знать типы сущностей, связанных ранее с этими именами
  • Будем поддерживать доп. структуру данных, которую будем заполнять именами с соответствующими им типами в процессе обхода дерева программы

Пример на Си

int dostrange(int a, char i) {
    // point 0
    int c = 5;
    for (int i = 0; i < 15; ++i) {
        i -= 1; // point 1
        long r = i;
        i *= r; // point 2
    }
    return a + (int)i * c; // point 3
}

Контекст типизации в каждой из точек:

0. a^{\mathrm{int}},\ i^{\mathrm{char}}

1. a^{\mathrm{int}},\ c^{\mathrm{int}},\ i^{\mathrm{int}}

2. a^{\mathrm{int}},\ c^{\mathrm{int}},\ i^{\mathrm{int}},\ r^{\mathrm{long}}

3. a^{\mathrm{int}},\ c^{\mathrm{int}},\ i^{\mathrm{char}}

+ Глобальные определения: <, +, *… — это наши константы

Контекст типизации: пример на лямбда-исчислении

В чистом лямбда-исчислении имена вводятся с помощью абстракции: \lambda f^A\,g^B\,x^C.\,f\ (g\ x) по Чёрчу.

λf:A
λg:B
λx:C
@
f
@
g
x
(0)
(1)
(2)
(∗)

Контекст типизации для каждой вершины:

(0) пустой

(1) f^A

(2) f^A,\ g^B

(∗) f^A,\ g^B,\ x^C — и во всех вершинах ниже

Правила типизации \lambda_\to

  • Δ Синтаксис типов \ T ::= \alpha \ \mid\ T\to T — буквы и стрелка, стрелка группируется вправо; наше расширение — ещё константы \mathrm{Int} и \mathrm{Bool}. Произвольный тип обозначаем A, B, C
  • Δ Утверждение типизации \Gamma \vdash M : A — «в контексте \Gamma терм M имеет тип A»
  • Δ Контекст \Gamma — объявления x^A через запятую, переменные различны

Вспомните правила с лекции: по одному на каждый вид терма.

Правила с лекции

\dfrac{x^A \in \Gamma}{\Gamma \vdash x : A}\ {\scriptstyle (\text{акс})} \dfrac{\Gamma \vdash M : A \to B \quad \Gamma \vdash N : A}{\Gamma \vdash M\ N : B}\ {\scriptstyle (\to E)} \dfrac{\Gamma,\ x^A \vdash M : B}{\Gamma \vdash \lambda x.\,M : A \to B}\ {\scriptstyle (\to I)}

  • По Чёрчу отличается только запись абстракции в (\to I): \ \lambda x^A.\,M вместо \lambda x.\,M

Правила для констант + наше расширение

Константы — тоже вершины дерева, и у каждого вида своё правило: литералы и \mathtt{true}/\mathtt{false} — листья, операторы и \mathtt{if} — вершины с поддеревьями.

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

\dfrac{n \in \{\mathtt{0}, \mathtt{1}, \mathtt{2}, \ldots\}}{\Gamma \vdash n : \mathrm{Int}}\ {\scriptstyle (\text{нат})} \dfrac{\Gamma \vdash M : \mathrm{Int} \quad \Gamma \vdash N : \mathrm{Int}}{\Gamma \vdash M \mathrel{\mathtt{+}} N : \mathrm{Int}}\ {\scriptstyle (\mathtt{+})}

Були

\dfrac{}{\Gamma \vdash \mathtt{true} : \mathrm{Bool}}\ {\scriptstyle (\text{бул})} \qquad \dfrac{}{\Gamma \vdash \mathtt{false} : \mathrm{Bool}}\ {\scriptstyle (\text{бул})} \dfrac{\Gamma \vdash M : \mathrm{Int} \quad \Gamma \vdash N : \mathrm{Int}}{\Gamma \vdash M \mathrel{\mathtt{<}} N : \mathrm{Bool}}\ {\scriptstyle (\mathtt{<})}

\dfrac{\Gamma \vdash C : \mathrm{Bool} \quad \Gamma \vdash M : A \quad \Gamma \vdash N : A}{\Gamma \vdash \mathtt{if}\ C\ M\ N : A}\ {\scriptstyle (\mathtt{if})}

  • Правила для \mathtt{-}, \mathtt{*} — как (\mathtt{+}); для \mathtt{==} — как (\mathtt{<})

Проверка типа: от дерева терма к дереву вывода

Проверим догадку формально: \ \vdash \lambda f\,x.\,f\ (f\ x) : (\alpha\to\alpha)\to\alpha\to\alpha. Начнём с дерева терма.

\dfrac{x^A \in \Gamma}{\Gamma \vdash x : A}\ {\scriptstyle (\text{акс})} \dfrac{\Gamma \vdash M : A \to B \quad \Gamma \vdash N : A}{\Gamma \vdash M\ N : B}\ {\scriptstyle (\to E)} \dfrac{\Gamma,\ x^A \vdash M : B}{\Gamma \vdash \lambda x.\,M : A \to B}\ {\scriptstyle (\to I)}

λf
λx
@
f
@
f
x

Проверка типа: от дерева терма к дереву вывода

Перевернём: корень внизу, листья наверху — так растут деревья вывода.

\dfrac{x^A \in \Gamma}{\Gamma \vdash x : A}\ {\scriptstyle (\text{акс})} \dfrac{\Gamma \vdash M : A \to B \quad \Gamma \vdash N : A}{\Gamma \vdash M\ N : B}\ {\scriptstyle (\to E)} \dfrac{\Gamma,\ x^A \vdash M : B}{\Gamma \vdash \lambda x.\,M : A \to B}\ {\scriptstyle (\to I)}

λf
λx
@
f
@
f
x

Проверка типа: от дерева терма к дереву вывода

Подпишем в каждой вершине её подтерм целиком: именно про него будет говорить утверждение типизации в этой вершине.

\dfrac{x^A \in \Gamma}{\Gamma \vdash x : A}\ {\scriptstyle (\text{акс})} \dfrac{\Gamma \vdash M : A \to B \quad \Gamma \vdash N : A}{\Gamma \vdash M\ N : B}\ {\scriptstyle (\to E)} \dfrac{\Gamma,\ x^A \vdash M : B}{\Gamma \vdash \lambda x.\,M : A \to B}\ {\scriptstyle (\to I)}

λf x. f (f x)
λx. f (f x)
f (f x)
f
f x
f
x

Проверка типа: от дерева терма к дереву вывода

Применяем правила снизу вверх. Выбора нет: вид подтерма — абстракция, аппликация или переменная — сам называет правило. Здесь \Gamma \equiv f^{\alpha\to\alpha},\ x^\alpha.

\dfrac{x^A \in \Gamma}{\Gamma \vdash x : A}\ {\scriptstyle (\text{акс})} \dfrac{\Gamma \vdash M : A \to B \quad \Gamma \vdash N : A}{\Gamma \vdash M\ N : B}\ {\scriptstyle (\to E)} \dfrac{\Gamma,\ x^A \vdash M : B}{\Gamma \vdash \lambda x.\,M : A \to B}\ {\scriptstyle (\to I)}

\Gamma \vdash f : \alpha\to\alpha (акс)

\Gamma \vdash f : \alpha\to\alpha (акс)

\Gamma \vdash x : \alpha (акс)

\Gamma \vdash f\ x : \alpha (→E)

f^{\alpha\to\alpha},\ x^\alpha \vdash f\ (f\ x) : \alpha (→E)

f^{\alpha\to\alpha} \vdash \lambda x.\,f\ (f\ x) : \alpha\to\alpha (→I)

\vdash \lambda f\,x.\,f\ (f\ x) : (\alpha\to\alpha)\to\alpha\to\alpha (→I)

шаги

1. λf — абстракция: (→I), f^{\alpha\to\alpha} уходит в контекст

2. λx — абстракция: (→I), в контекст уходит x^\alpha

3. f (f x) — аппликация: (→E), две ветки

4. f — переменная: (акс), f^{\alpha\to\alpha} \in \Gamma

5. f x — аппликация: (→E), снова две ветки

6. f — переменная: (акс)

7. x — переменная: (акс), x^\alpha \in \Gamma — все листья закрыты

Утверждение проверено: дерево вывода — это перевёрнутое дерево терма, в котором каждая вершина снабжена контекстом и типом.

Дерево вывода с дырками

Заполните пронумерованные пропуски — типы и названия правил — в дереве вывода для

\ \vdash \lambda x.\,\mathtt{if}\ (x \mathrel{\mathtt{<}} \mathtt{2})\ (x \mathrel{\mathtt{+}} \mathtt{1})\ \mathtt{0} : \mathrm{Int}\to\mathrm{Int}.

\dfrac{ \dfrac{ \dfrac{ \dfrac{}{\Gamma \vdash x : \square_5}\ {\scriptstyle (\text{акс})} \quad \dfrac{}{\Gamma \vdash \mathtt{2} : \mathrm{Int}}\ {\scriptstyle \square_4} }{\Gamma \vdash x \mathrel{\mathtt{<}} \mathtt{2} : \square_3}\ {\scriptstyle (\mathtt{<})} \qquad \dfrac{ \dfrac{}{\Gamma \vdash x : \mathrm{Int}}\ {\scriptstyle \square_7} \quad \dfrac{}{\Gamma \vdash \mathtt{1} : \mathrm{Int}}\ {\scriptstyle (\text{нат})} }{\Gamma \vdash x \mathrel{\mathtt{+}} \mathtt{1} : \mathrm{Int}}\ {\scriptstyle \square_6} \qquad \dfrac{}{\Gamma \vdash \mathtt{0} : \square_8}\ {\scriptstyle (\text{нат})} }{x^{\square_1} \vdash \mathtt{if}\ (x \mathrel{\mathtt{<}} \mathtt{2})\ (x \mathrel{\mathtt{+}} \mathtt{1})\ \mathtt{0} : \mathrm{Int}}\ {\scriptstyle \square_2} }{\vdash \lambda x.\,\mathtt{if}\ (x \mathrel{\mathtt{<}} \mathtt{2})\ (x \mathrel{\mathtt{+}} \mathtt{1})\ \mathtt{0} : \mathrm{Int}\to\mathrm{Int}}\ {\scriptstyle (\to I)}

Здесь \Gamma \equiv x^{\mathrm{Int}}.

\square_1 = \mathrm{Int} (контекст после (\to I)); \ \square_2 = (\mathtt{if}); \ \square_3 = \mathrm{Bool}; \ \square_4 = (\text{нат}); \ \square_5 = \mathrm{Int}; \ \square_6 = (\mathtt{+}); \ \square_7 = (\text{акс}); \ \square_8 = \mathrm{Int} — ветки \mathtt{if} одного типа.

Дерево вывода с нуля

Постройте дерево вывода для \ \vdash \lambda x\,f.\,f\ x : \alpha\to(\alpha\to\beta)\to\beta

Пусть \Gamma \equiv x^\alpha,\ f^{\alpha\to\beta}.

\dfrac{ \dfrac{ \dfrac{ \dfrac{}{\Gamma \vdash f : \alpha\to\beta}\ {\scriptstyle (\text{акс})} \quad \dfrac{}{\Gamma \vdash x : \alpha}\ {\scriptstyle (\text{акс})} }{x^\alpha,\ f^{\alpha\to\beta} \vdash f\ x : \beta}\ {\scriptstyle (\to E)} }{x^\alpha \vdash \lambda f.\,f\ x : (\alpha\to\beta)\to\beta}\ {\scriptstyle (\to I)} }{\vdash \lambda x\,f.\,f\ x : \alpha\to(\alpha\to\beta)\to\beta}\ {\scriptstyle (\to I)}

Верно ли утверждение?

Поднимите руку, если утверждение выводимо:

(а) \ \vdash \mathbf{K} : \alpha\to\alpha\to\alpha

(б) \ \vdash \mathbf{K} : \alpha\to\alpha\to\beta

(в) \ a^{\alpha\to\alpha} \vdash \mathbf{I}\ a : \alpha\to\alpha

(г) \ a^{\alpha\to\alpha} \vdash a\ b : \alpha

(д) \ \vdash \lambda x.\,x : \mathrm{Int}\to\mathrm{Int}

(е) \ \vdash \lambda x.\,x \mathrel{\mathtt{+}} \mathtt{1} : \alpha\to\alpha

Выводимы (а), (в), (д). В (а) тип не наиболее общий, но дерево строится. В (б) результат — первый аргумент, он типа \alpha. В (г) переменной b нет в контексте. (д) и (е) — снова буквы против констант: вместо буквы \mathrm{Int} подставить можно, а сделать из \mathrm{Int} букву — нет.

Простые примеры задач населения

Найдите все термы M: \ x^\beta \vdash M : \alpha \to \alpha

Найдите все термы M: \ x^\beta \vdash M : \alpha \to \beta

Найдите все термы M: \ x^\beta \vdash M : \alpha \to \gamma

  • Единственный терм типа \alpha передаётся аргументом — обязаны его вернуть: M \equiv \lambda y.\,y
  • Единственное значение типа \beta есть в контексте — обязаны вернуть его: M \equiv \lambda y.\,x
  • Значение типа \gamma брать неоткуда: этот тип не населён в данном контексте

Сколько обитателей у типа?

Сколько замкнутых термов в нормальной форме у каждого типа?

(а) \alpha\to\alpha\to\alpha

(б) (\alpha\to\alpha)\to\alpha\to\alpha

(в) \alpha

(г) \mathrm{Int}

  • (а) Два: \lambda x\,y.\,x и \lambda x\,y.\,y — больше взять значение типа \alpha неоткуда
  • (б) Бесконечно много: \lambda s\,z.\,z, \ \lambda s\,z.\,s\ z, \ \lambda s\,z.\,s\ (s\ z), … — это числа Чёрча!
  • (в) Ни одного: у буквы нет своих значений, а аргументов, из которых её можно взять, нет
  • (г) Бесконечно много: 0, 1, 2, \ldots — у константного типа значения свои

Рекурсия возвращается: fix факультативно

  • \mathbf{Y} := \lambda f.\,(\lambda x.\,f\ (x\ x))\ (\lambda x.\,f\ (x\ x)) содержит x\ x — в \lambda_\to у него нет типа, как и у любого другого комбинатора неподвижной точки: все программы \lambda_\to завершаются
  • Значит, рекурсию встраиваем в язык так же, как числа и були: новая форма терма \mathtt{fix}\ M, от которой нужно ровно свойство \mathbf{Y} из П2 — \mathtt{fix}\ M =_\beta M\ (\mathtt{fix}\ M)

Задайте \mathtt{fix} полностью: (а) строку в грамматике термов; (б) правило типизации; (в) правило редукции.

(а) \ M ::= \ldots \ \mid\ \mathtt{fix}\ M \qquad (б) \ \dfrac{\Gamma \vdash M : A \to A}{\Gamma \vdash \mathtt{fix}\ M : A}\ {\scriptstyle (\mathtt{fix})}\qquad (в) \ \mathtt{fix}\ M \rightsquigarrow M\ (\mathtt{fix}\ M)

Аргумент \mathtt{fix} — функция от «самой себя»: тип A \to A; результат — неподвижная точка, тип A.

Факториал в расширенном языке факультативно

\mathbf{fac} := \mathtt{fix}\ F, \qquad F \equiv \lambda f\,n.\,\mathtt{if}\ (n \mathrel{\mathtt{==}} \mathtt{0})\ \mathtt{1}\ (n \mathrel{\mathtt{*}} f\ (n \mathrel{\mathtt{-}} \mathtt{1}))

  • Как в П2: рекурсивный вызов абстрагирован по имени f — только вместо \mathbf{Y} стоит \mathtt{fix}, а вместо кодировок — константы

Какой тип у F? У \mathbf{fac}?

F : (\mathrm{Int}\to\mathrm{Int})\to\mathrm{Int}\to\mathrm{Int} — принимает «себя» и число; по правилу (\mathtt{fix}) с A := \mathrm{Int}\to\mathrm{Int} получаем \mathbf{fac} : \mathrm{Int}\to\mathrm{Int}.

Дерево типизации факториала факультативно

F \equiv \lambda f\,n.\,\mathtt{if}\ (n \mathrel{\mathtt{==}} \mathtt{0})\ \mathtt{1}\ (n \mathrel{\mathtt{*}} f\ (n \mathrel{\mathtt{-}} \mathtt{1})), \quad \Gamma \equiv f^{\mathrm{Int}\to\mathrm{Int}},\ n^{\mathrm{Int}}

\Gamma \vdash n : \mathrm{Int} (акс)

\Gamma \vdash \mathtt{0} : \mathrm{Int} (нат)

\Gamma \vdash n \mathrel{\mathtt{==}} \mathtt{0} : \mathrm{Bool} (==)

\Gamma \vdash \mathtt{1} : \mathrm{Int} (нат)

\Gamma \vdash n : \mathrm{Int} (акс)

\Gamma \vdash f : \mathrm{Int}\to\mathrm{Int} (акс)

\Gamma \vdash n : \mathrm{Int} (акс)

\Gamma \vdash \mathtt{1} : \mathrm{Int} (нат)

\Gamma \vdash n \mathrel{\mathtt{-}} \mathtt{1} : \mathrm{Int} (−)

\Gamma \vdash f\ (n \mathrel{\mathtt{-}} \mathtt{1}) : \mathrm{Int} (→E)

\Gamma \vdash n \mathrel{\mathtt{*}} f\ (n \mathrel{\mathtt{-}} \mathtt{1}) : \mathrm{Int} (∗)

\Gamma \vdash \mathtt{if}\ (n \mathrel{\mathtt{==}} \mathtt{0})\ \mathtt{1}\ (n \mathrel{\mathtt{*}} f\ (n \mathrel{\mathtt{-}} \mathtt{1})) : \mathrm{Int} (if)

f^{\mathrm{Int}\to\mathrm{Int}} \vdash \lambda n.\,\mathtt{if}\ \ldots : \mathrm{Int}\to\mathrm{Int} (→I)

\vdash F : (\mathrm{Int}\to\mathrm{Int})\to\mathrm{Int}\to\mathrm{Int} (→I)

\vdash \mathtt{fix}\ F : \mathrm{Int}\to\mathrm{Int} (fix)

Как работает fix: fac 2 факультативно

Раскрыли \mathbf{fac}; дальше единственное правило для \mathtt{fix} — редукция \mathtt{fix}\ M \rightsquigarrow M\ (\mathtt{fix}\ M).

fix F 2

цепочка

\mathbf{fac}\ \mathtt{2} \rightsquigarrow_{\mathbf{fac}} \mathtt{fix}\ F\ \mathtt{2}

Как работает fix: fac 2 факультативно

Правило (\mathtt{fix}): наружу вышла копия F, внутри остался \mathtt{fix}\ F — запас на следующий виток.

F (fix F) 2

цепочка

\mathbf{fac}\ \mathtt{2} \rightsquigarrow_{\mathbf{fac}} \mathtt{fix}\ F\ \mathtt{2}

\rightsquigarrow_{\mathtt{fix}}\ F\ (\mathtt{fix}\ F)\ \mathtt{2}

Как работает fix: fac 2 факультативно

Два β-шага: f стало \mathtt{fix}\ F, n стало \mathtt{2}. Перед нами тело F — обычный \mathtt{if} с константами.

if (2 == 0) 1 (2 * fix F (2 − 1))

цепочка

\mathbf{fac}\ \mathtt{2} \rightsquigarrow_{\mathbf{fac}} \mathtt{fix}\ F\ \mathtt{2}

\rightsquigarrow_{\mathtt{fix}}\ F\ (\mathtt{fix}\ F)\ \mathtt{2}

\twoheadrightarrow_\beta\ \mathtt{if}\ (\mathtt{2} \mathrel{\mathtt{==}} \mathtt{0})\ \mathtt{1}\ (\mathtt{2} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ (\mathtt{2} \mathrel{\mathtt{-}} \mathtt{1}))

Как работает fix: fac 2 факультативно

Константы считаются за шаг: \mathtt{2} \mathrel{\mathtt{==}} \mathtt{0} \rightsquigarrow \mathtt{false}, \mathtt{if} выбрал ветку else, \mathtt{2} \mathrel{\mathtt{-}} \mathtt{1} \rightsquigarrow \mathtt{1}. Снаружи остался \mathtt{2} \mathrel{\mathtt{*}}, внутри — тот же кадр, что в начале.

2 * fix F 1

цепочка

\mathbf{fac}\ \mathtt{2} \rightsquigarrow_{\mathbf{fac}} \mathtt{fix}\ F\ \mathtt{2}

\rightsquigarrow_{\mathtt{fix}}\ F\ (\mathtt{fix}\ F)\ \mathtt{2}

\twoheadrightarrow_\beta\ \mathtt{if}\ (\mathtt{2} \mathrel{\mathtt{==}} \mathtt{0})\ \mathtt{1}\ (\mathtt{2} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ (\mathtt{2} \mathrel{\mathtt{-}} \mathtt{1}))

\twoheadrightarrow\ \mathtt{2} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ \mathtt{1} — правила (\mathtt{==}), (\mathtt{if}), (\mathtt{-})

Как работает fix: fac 2 факультативно

Второй виток: снова (\mathtt{fix}), β-шаги и условие; снаружи ещё один множитель.

2 * 1 * fix F 0

цепочка

\mathbf{fac}\ \mathtt{2} \rightsquigarrow_{\mathbf{fac}} \mathtt{fix}\ F\ \mathtt{2}

\rightsquigarrow_{\mathtt{fix}}\ F\ (\mathtt{fix}\ F)\ \mathtt{2}

\twoheadrightarrow_\beta\ \mathtt{if}\ (\mathtt{2} \mathrel{\mathtt{==}} \mathtt{0})\ \mathtt{1}\ (\mathtt{2} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ (\mathtt{2} \mathrel{\mathtt{-}} \mathtt{1}))

\twoheadrightarrow\ \mathtt{2} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ \mathtt{1} — правила (\mathtt{==}), (\mathtt{if}), (\mathtt{-})

\twoheadrightarrow\ \mathtt{2} \mathrel{\mathtt{*}} \mathtt{1} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ \mathtt{0} — виток 2

Как работает fix: fac 2 факультативно

Третий виток: копия, β-шаги — и на этот раз \mathtt{0} \mathrel{\mathtt{==}} \mathtt{0} истинно.

2 * 1 * if (0 == 0) 1 (0 * fix F (0 − 1))

цепочка

\mathbf{fac}\ \mathtt{2} \rightsquigarrow_{\mathbf{fac}} \mathtt{fix}\ F\ \mathtt{2}

\rightsquigarrow_{\mathtt{fix}}\ F\ (\mathtt{fix}\ F)\ \mathtt{2}

\twoheadrightarrow_\beta\ \mathtt{if}\ (\mathtt{2} \mathrel{\mathtt{==}} \mathtt{0})\ \mathtt{1}\ (\mathtt{2} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ (\mathtt{2} \mathrel{\mathtt{-}} \mathtt{1}))

\twoheadrightarrow\ \mathtt{2} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ \mathtt{1} — правила (\mathtt{==}), (\mathtt{if}), (\mathtt{-})

\twoheadrightarrow\ \mathtt{2} \mathrel{\mathtt{*}} \mathtt{1} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ \mathtt{0} — виток 2

\twoheadrightarrow\ \mathtt{2} \mathrel{\mathtt{*}} \mathtt{1} \mathrel{\mathtt{*}} \mathtt{if}\ (\mathtt{0} \mathrel{\mathtt{==}} \mathtt{0})\ \mathtt{1}\ (\mathtt{0} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ (\mathtt{0} \mathrel{\mathtt{-}} \mathtt{1})) — виток 3

Как работает fix: fac 2 факультативно

\mathtt{if} вернул \mathtt{1}, ветка с \mathtt{fix}\ F\ (\mathtt{0} \mathrel{\mathtt{-}} \mathtt{1}) выброшена, так и не раскрывшись: \mathtt{fix} разворачивался ровно столько раз, сколько потребовало условие.

2 * 1 * 1 ↠ 2

цепочка

\mathbf{fac}\ \mathtt{2} \rightsquigarrow_{\mathbf{fac}} \mathtt{fix}\ F\ \mathtt{2}

\rightsquigarrow_{\mathtt{fix}}\ F\ (\mathtt{fix}\ F)\ \mathtt{2}

\twoheadrightarrow_\beta\ \mathtt{if}\ (\mathtt{2} \mathrel{\mathtt{==}} \mathtt{0})\ \mathtt{1}\ (\mathtt{2} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ (\mathtt{2} \mathrel{\mathtt{-}} \mathtt{1}))

\twoheadrightarrow\ \mathtt{2} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ \mathtt{1} — правила (\mathtt{==}), (\mathtt{if}), (\mathtt{-})

\twoheadrightarrow\ \mathtt{2} \mathrel{\mathtt{*}} \mathtt{1} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ \mathtt{0} — виток 2

\twoheadrightarrow\ \mathtt{2} \mathrel{\mathtt{*}} \mathtt{1} \mathrel{\mathtt{*}} \mathtt{if}\ (\mathtt{0} \mathrel{\mathtt{==}} \mathtt{0})\ \mathtt{1}\ (\mathtt{0} \mathrel{\mathtt{*}} \mathtt{fix}\ F\ (\mathtt{0} \mathrel{\mathtt{-}} \mathtt{1})) — виток 3

\twoheadrightarrow\ \mathtt{2} \mathrel{\mathtt{*}} \mathtt{1} \mathrel{\mathtt{*}} \mathtt{1} \twoheadrightarrow \mathtt{2} — правила (\mathtt{==}), (\mathtt{if}), (\mathtt{*})

Сегодня мы многое поняли

  • Анализ типов — по правилам расставляем метки — просто!
  • Научили мозг проверке типов — отличать хорошие программы от плохих
  • Объяснить по всей строгости, почему программа типизируется или нет

Таинственные связи ☁

Дерево вывода типа комбинатора \mathbf{B} для произвольных типов A, B, C — или вывод силлогизма в исчислении высказываний?

\dfrac{ \dfrac{ \dfrac{ \dfrac{ \dfrac{}{\Gamma \vdash {\color{blue}f :}\ B\to C}\ {\scriptstyle (\text{акс})} \qquad \dfrac{ \dfrac{}{\Gamma \vdash {\color{blue}g :}\ A\to B}\ {\scriptstyle (\text{акс})} \quad \dfrac{}{\Gamma \vdash {\color{blue}x :}\ A}\ {\scriptstyle (\text{акс})} }{\Gamma \vdash {\color{blue}g\ x :}\ B}\ {\scriptstyle (\to E)} }{{\color{blue}f}^{B\to C},\ {\color{blue}g}^{A\to B},\ {\color{blue}x}^{A} \vdash {\color{blue}f\ (g\ x) :}\ C}\ {\scriptstyle (\to E)} }{{\color{blue}f}^{B\to C},\ {\color{blue}g}^{A\to B} \vdash {\color{blue}\lambda x.\,f\ (g\ x) :}\ A\to C}\ {\scriptstyle (\to I)} }{{\color{blue}f}^{B\to C} \vdash {\color{blue}\lambda g\,x.\,f\ (g\ x) :}\ (A\to B)\to A\to C}\ {\scriptstyle (\to I)} }{\vdash {\color{blue}\lambda f\,g\,x.\,f\ (g\ x) :}\ (B\to C)\to(A\to B)\to A\to C}\ {\scriptstyle (\to I)}

Таинственные связи ☁

Дерево вывода типа комбинатора \mathbf{B} для произвольных типов A, B, C — или вывод силлогизма в исчислении высказываний?

\dfrac{ \dfrac{ \dfrac{ \dfrac{ \dfrac{}{\Gamma \vdash \phantom{f :}\ B\to C}\ {\scriptstyle (\text{акс})} \qquad \dfrac{ \dfrac{}{\Gamma \vdash \phantom{g :}\ A\to B}\ {\scriptstyle (\text{акс})} \quad \dfrac{}{\Gamma \vdash \phantom{x :}\ A}\ {\scriptstyle (\text{акс})} }{\Gamma \vdash \phantom{g\ x :}\ B}\ {\scriptstyle (\to E)} }{{\phantom{f}}^{B\to C},\ {\phantom{g}}^{A\to B},\ {\phantom{x}}^{A} \vdash \phantom{f\ (g\ x) :}\ C}\ {\scriptstyle (\to E)} }{{\phantom{f}}^{B\to C},\ {\phantom{g}}^{A\to B} \vdash \phantom{\lambda x.\,f\ (g\ x) :}\ A\to C}\ {\scriptstyle (\to I)} }{{\phantom{f}}^{B\to C} \vdash \phantom{\lambda g\,x.\,f\ (g\ x) :}\ (A\to B)\to A\to C}\ {\scriptstyle (\to I)} }{\vdash \phantom{\lambda f\,g\,x.\,f\ (g\ x) :}\ (B\to C)\to(A\to B)\to A\to C}\ {\scriptstyle (\to I)}

Материалы ☁

Типизация в стиле Карри и в стиле Чёрча

по Карри по Чёрчу
абстракция \lambda x.\,M \lambda x^A.\,M — в домашке \x:a. x
количество кода меньше кода, сложнее задача типовой подсистеме языка программисту приходится больше писать
сколько типов у терма либо 0, либо \infty либо 0, либо 1
алгоритм сложнее: приходится решать систему типовых уравнений (П13) проще: вся необходимая информация написана в терме
в вашем языке Haskell, OCaml; auto в C++, var в Java и C# типы параметров в C, Java, Go

Мотивация статического анализа программ

  • Программное обеспечение — это сложно
  • В программах случаются ошибки (постоянно и неизбежно)
  • Поиск ошибок может занимать годы, десятилетия, вечность
  • Нужны методы для того, чтобы судить о программах и их свойствах автоматически
  • Программы могут работать долго
  • Хотим отвергать «плохие» программы до исполнения (статически)

Роль типов в языках

  • Позволяют выявлять некоторые классы ошибок:

    GHCi> length 42         -- Ошибка: на вход не список, а число
    GHCi> foldr (+) "ABCDE" -- Ошибка: плюс работает на числах, но не символах
  • Механизм абстракции: интерфейс, скрывающий детали реализации

  • Средство документирования: как использовать сущность

  • Частичная спецификация поведения

  • Позволяют генерировать более эффективный низкоуровневый код

Ограниченность систем типов ☁

  • Хотелось бы, чтобы все «хорошие» программы типизировались, а все «плохие» — нет
  • Однако по теореме Успенского-Райса задача проверки любого нетривиального1 свойства программы неразрешима

В чем подвох с системами типов?

  • Всё, что типизируется, — «хорошее», но обратное не всегда верно
  • ⇒ Всегда существуют «хорошие» программы, которые не типизируются2

Деревья вывода

  • Теория доказательств математической логики — доказываем принадлежность утверждения типизации множеству истинных утверждений
  • Истинное утверждение в теории доказательств — то, которое можно синтаксически вывести по правилам вывода из аксиом
  • Деревья вывода — синтаксические, машинно-проверяемые конструкции
  • Деревья сами по себе не несут смысла, наличие корректного дерева говорит только о возможности его построения
  • Чтобы деревья имели смысл, для каждой теории доказываются утверждения вида:
    • Если существует дерево вывода с корнем X, то верно Y
    • e.g. Если существует \lambda_\to дерево с утверждением про терм M в корне (терм M в таком-то контексте имеет такой-то тип), то этот терм строго нормализуем
  • Иногда по правилам вывода очевиден алгоритм построения дерева
    • В нашем случае (для \lambda_\to) это так
    • В общем случае — нет (математики оказались бы не нужны)

Не всё сильно-нормализуемое типизируется ✱

Вспомним: всё, что типизируется, «хорошее», но обратное не всегда верно. Будем «хорошим» считать сильную нормализуемость.

\mathbf{2}\ \mathbf{K} терм сильно нормализуем и редуцируется так:

\mathbf{2}\ \mathbf{K} = \lambda z.\,\mathbf{K}\ (\mathbf{K}\ z) = \lambda z\,a\,b.\,z

Однако протипизировать это не получится.

  • Наиболее общий тип \mathbf{K} — это \gamma \to \tau \to \gamma
  • \mathbf{2} := \lambda s\,z.\,s\ (s\ z), наиболее общий тип \mathbf{2} — это (\alpha \to \alpha) \to \alpha \to \alpha
  • По правилу типизации аппликации, применение \mathbf{2} к \mathbf{K} накладывает на \gamma, \tau, \alpha ограничения \alpha = \gamma,\ \alpha = \tau \to \gamma

Противоречие.

Эквивалентности не сохраняют тип ✱

\beta-эквивалентность не сохраняет тип

Из лекции известно, что если M редуцируется к N и \Gamma \vdash M : \tau, то \Gamma \vdash N : \tau.

Неправда, что если M редуцируется к N и \Gamma \vdash N : \tau, то \Gamma \vdash M : \tau.

Пример: N = \lambda x.\,x, N : \alpha \to \alpha; M = \lambda x.\,(\lambda y.\,x)\ (x\ \mathbf{I}), и за одно редуцирование M переходит в N, но M : \alpha \to \alpha не выполняется: тогда бы подтерм x\ \mathbf{I} не типизировался, так как \alpha не является стрелочным типом.

\eta-эквивалентность не сохраняет тип

Если \lambda x.\,M\ x : \tau (где x не является свободным в M), то M : \tau.

Если M : \tau, то не обязательно \lambda x.\,M\ x : \tau.

Пример: \mathbf{I} : \alpha \to \alpha, но \lambda x\,y.\,x\ y : (\beta \to \gamma) \to (\beta \to \gamma) нельзя приписать тип \alpha \to \alpha: тогда бы подтерм x\ y не типизировался, так как \alpha не является стрелочным типом.

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

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