Математические модели вычислений
Расширения типизированного λ-исчисления
λ-исчисление с конструкторами типов
λ-исчисление с зависимыми типами
https://maxxk.github.io/formal-models/Система F (λ2, прошлое занятие)
τ≡ b | τ1→τ2 | α | ∀α.τ, b ∈ B.
[] wf |
Γ wf τ ∈ Γ |
---|
Γ, x : τ wf |
Γ wf α не входит в типы в Γ |
---|
Γ, α wf |
Γ, x : α wf |
---|
Γ, x : α ⊦ x : α |
Γ, x : σ ⊦ e : τ Γ wf |
---|
Γ ⊦ (λ xσ . e) : σ → τ |
Γ ⊦ x : σ → τ Γ ⊦ y : σ Γ wf |
---|
Γ ⊦ x · y : τ |
Γ wf α ∈ Γ |
---|
Γ ⊦ α : * |
Γ wf Γ ⊦ α : * Γ ⊦ β : * |
---|
Γ ⊦ α → β : * |
Γ, α wf Γ, α ⊦ β : * |
---|
Γ ⊦ ∀α.β : * |
Γ ⊦ ∀ α . β : * Γ, α : * ⊦ M : β |
---|
Γ ⊦ Λα.M : ∀α . β |
Γ ⊦ X : ∀α.β Γ ⊦ γ : * |
---|
Γ ⊦ X · γ : β[α := γ] |
Γ wf cα — постоянная типа α |
---|
Γ ⊦ c: α |
Пример: чего мы хотим добиться?
В прошлый раз мы рассматривали пример того, как λ-исчисление с простыми типами может использоваться для проверки типов простого императивного языка программирования. Проверялось, что функции передаются аргументы именно тех типов, которые ожидаются согласно её определению.
Более амбициозная задача для проверки типов: контролировать нетривиальные свойства кода, например, определять при компиляции ошибки типа «выход за границы массива».
Пример: метод прогонки
// Tridiagonal algorithm (solution for the tridiagonal system of linear equations)
// (d, c, e) - matrix of the linear system
// n - matrix size
// d - diagonal elements
// c - subdiagonal elements
// e - superdiagonal elements
// a - superdiagonal gaussian elimination coefficients
// b - right hand side
// x - answer
void tridiagonal(int n, double* d, double* c,
double* e, double* a, double* b, int* x) {
a[1] = -e[0] / d[0];
b[1] = b[0] / d[0];
for (int i=1; i < n; i++) {
double denominator = d[i] + c[i]*a[i];
a[i+1] = -e[i] / denominator;
b[i+1] = (1. - c[i])*b[i] / denominator;
}
x[n] = (1. - c[n])*b[n]*(d[n] + c[n]*a[n]);
for (int j=n-1; j >=0; j--) {
x[j] = a[j]*x[j+1] + b[j+1];
}
}
Прогонка с простыми типами
Variable (int double DoubleArray IntArray : Type).
Variable (toInt : nat -> int) (toDouble : nat -> double).
Variable (set : DoubleArray -> int -> double -> unit)
(get : DoubleArray -> int -> double)
(add sub div mul : double -> double -> double)
(neg : double -> double)
(inc dec : int -> int)
(loop : int -> int -> (int -> unit) -> unit).
Definition tridiagonal (n : int) (d c e a b : DoubleArray) (x : IntArray) := (
set a (toInt 1) (div (neg (get e (toInt 0))) (get d (toInt 0))),
set b (toInt 1) (div (get b (toInt 0)) (get d (toInt 0))),
loop (toInt 1) n (fun i => let
denominator := (add (get d i) (mul (get c i) (get a i))) in
let body := (
set a (inc i) (div (get e i) denominator),
set b (inc i) (div (mul (sub (toDouble 1) (get c i)) (get b i)) denominator)
)
in tt),
set x n (mul (mul (sub (toDouble 1) (get c n)) (get b n)) (add (get d n) (mul (get c n) (get a n)))),
loop (dec n) (toInt 0) (fun j => let
body := set x j (add (get b (inc j)) (mul (get a j) (get x (inc i))))
in tt)
).
Операторы над типами
Как определить тип абстрактного массива?
λ (x : α). M — абстракция термов по термам λ (x : int). x
Λ (α : *). M — абстракция термов по типам Λ (α : *) (β : *) . λ (p : α × β) . fst p
Требуется — абстракция типов по типам
α — тип (type) * — вид (kind, «тип типов»)
Для абстракции типов по типам нужно «перенести» λ-исчисление с простыми типами на уровень выше («λ-исчисление с простыми видами»).
κ = * | κ1 ⇒ κ2
⇒ — временно обозначим «стрелку» между видами
Абстрактный массив: Array : * ⇒ * — конструктор типа Array · double : * — тип
Исчисление λω
κ = * | κ₁ ⇒ κ₂
Введение:
Γ, α : κ₁ ⊦ A : κ₂ |
---|
Γ ⊦ λ (α : κ₁).A : κ₁ ⇒ κ₂ |
Удаление:
Γ ⊦ A : κ1 ⇒ κ2 Γ ⊦ B : κ1 |
---|
Γ ⊦ A · B : κ2 |
Дополнение существующих правил: ∀ (α : κ) . …, Λ (α : κ) . …
β-редукция распространяется теперь и на типы.
Эквивалентность типов в λω
Типы α и (λ(t : *).t) · α должны быть эквивалентны.
Эквивалентность типов определяется в терминах нормализации. Пусть исчисление сильно нормализуемо (для λ-исчисления с простыми видами, это верно). Тогда два типа одного вида эквивалентны, если они приводятся β-редукцией к одинаковой нормальной форме.
Примеры типов в λω
Массив с операциями:
Array : * ⇒ *
new : ∀ (α : *). int -> Array · α
get : ∀(α : *). Array · α → int → α
set : ∀(α : *). Array · α → int → α → T
Прогонка в λω
Variable (int double : Type).
Variable (Array : Type -> Type)
(set : forall {T : Type}, Array T -> int -> T -> unit)
(get : forall {T : Type}, Array T -> int -> T).
Variable (toInt : nat -> int) (toDouble : nat -> double).
Variable
(add sub div mul : double -> double -> double)
(neg : double -> double)
(inc dec : int -> int)
(loop : int -> int -> (int -> unit) -> unit).
Definition tridiagonal (n : int) (d c e a b : Array double) (x : Array int) := (
set a (toInt 1) (div (neg (get e (toInt 0))) (get d (toInt 0))),
set b (toInt 1) (div (get b (toInt 0)) (get d (toInt 0))),
loop (toInt 1) n (fun i => let
denominator := (add (get d i) (mul (get c i) (get a i))) in
let body := (
set a (inc i) (div (get e i) denominator),
set b (inc i) (div (mul (sub (toDouble 1) (get c i)) (get b i)) denominator)
)
in tt),
set x n (mul (mul (sub (toDouble 1) (get c n)) (get b n)) (add (get d n) (mul (get c n) (get a n)))),
loop (dec n) (toInt 0) (fun j => let
body := set x j (add (get b (inc j)) (mul (get a j) (get x (inc j))))
in tt)
).
Соответствие Карри-Говарда в λω
Тип равенств между типами (тип идентичности по Лейбницу):
Eq = λ (α β : *). ∀ (φ : * ⇒ *). φ α → φ β
Свойство рефлексивности задаёт конструктор равенства («введение»):
refl : ∀(α : *). Eq α α
refl = Λ(α : *). Λ(φ : * ⇒ *) . λ (x : φ α) . x
Доказуемы свойства симметричности и транзитивности:
symm : ∀ (α β : *). Eq α β → Eq β α
trans : ∀ (α β γ : *) . Eq α β → Eq β γ → Eq α γ
Доказательства:
symm = Λ(α β : *). λ (e : Eq α β). e (λ (γ : *). Eq γ α) (refl α)
trans = Λ(α β γ : *). λ (s : Eq α β) (t : Eq β γ). t (Eq α) s
Свойство подстановки по равенству:
lift : ∀ (α β : * ). ∀ (φ : * ⇒ *). Eq α β → Eq (φ α) (φ β)
lift = Λ (α β : *). Λ (φ : * ⇒ *). λ (e : Eq α β). e (λ (γ : *). Eq (φ α) (φ γ)) (refl (φ α))
К зависимым типам
Мы определили следующие виды абстракции:
λ (x : α). M — терм ↦ терм λ (x : int). x
Λ (α : *). M — тип ↦ терм Λ (α : *) (β : *) . λ (p : α × β) . fst p
λ (x : κ). A — тип ↦ тип Eq = λ (α β : *). ∀ (φ : * ⇒ *). φ α → φ β
Соответствие Карри-Говарда для рассмотренных систем позволяет представлять в виде термов исчисление высказываний и различные его расширения.
Для того, чтобы замкнуть граф возможных абстракций и перейти в исчисление предикатов не хватает последнего ребра:
терм ↦ тип
λ-куб Барендрегта
Barendregt H.P. Introduction to generalized type systems // J. Funct. Program. 1991. Vol. 1, № 2. P. 125–154. Общая схема, примеры, ссылки на доказательства сильной нормализации для всех систем куба.
Зависимое произведение
Π(x : α). A
α и A — типы, вместо x подставляется терм.
Массив с контролем границ
Array : * ⇒ int ⇒ *
Array double 0
Array double 10
Функция, связывающая n и длину массива:
Π (n : int) (Array double n) → Array double n
λ (n : int). (d : Array double n). d
Исчисление Конструкций (λPω)
τ = … | Π (x : α). A …
Γ, x : α ⊦ A : * Γ ⊦ α : * |
---|
Γ ⊦ Π (x : α). A : * |
Γ ⊦ Π (x : α). A : * Γ, x : α ⊦ M : A |
---|
Γ ⊦ λ (x : α). M : Π (x : α). A |
Γ ⊦ F : Π (x : α). A Γ ⊦ N : α |
---|
Γ ⊦ F · N : A[x := α] |
Все виды абстракций — в один
- Π (x : α). β, где β не зависит от x — то же самое, что α → β.
- Π (x : κ). N, где κ — вид.
τ = * | b ∈ B | Π (x : A). B | α
[] wf |
Γ wf τ ∈ Γ |
---|
Γ, x : τ wf |
Γ wf α не входит в типы в Γ |
---|
Γ, α wf |
Γ, x : α wf |
---|
Γ, x : α ⊦ x : α |
Γ, x : α ⊦ A : * Γ ⊦ α : * |
---|
Γ ⊦ Π (x : α). A : * |
Γ ⊦ Π (x : α). A : * Γ, x : α ⊦ M : A |
---|
Γ ⊦ λ (x : α). M : Π (x : α). A |
Γ ⊦ F : Π (x : α). A Γ ⊦ N : α |
---|
Γ ⊦ F · N : A[x := α] |
Γ wf cα — постоянная типа α |
---|
Γ ⊦ c: α |
Γ wf |
---|
Γ ⊦ * : * |
Универсумы
В чём «физический смысл» этого правила: Γ ⊦ * : *
Тип функции на значениях:
id ≡ λ(x : ℕ).x : ℕ → ℕ (≡ Π(x : ℕ).ℕ, что допустимо т.к. ℕ : *).
Обобщённый список (полиморфное кодирование, как в System Fω):
List T ≡ λ(T : *). Π(R : *). R → (T → R → R) → R
List T : ?
С последней частью всё понятно: R : *, T : * ⊢ R → (T → R → R) → R : *
Для следующей части: Π(R : *). R → (T → R → R) → R
нужно: Π (x : α). A : * ⊣ α : *, x : α ⊦ A : *
С нашим правилом: α : * ≡ * : *
Итого: List : Π(T : *).*
Парадокс Бурали-Форти в исчислении конструкций
Type — универсум («тип всех типов»), в общем случаи такие конструкции опасны, т.к. позволяют определить парадоксы.
Γ wf |
---|
Γ ⊦ Type : Type |
— рекурсивное включение универсумов, которое мы дали в первом правиле редукции сегодняшней лекции:
Type : Type.
Оно делает противоречивым даже System Fω — полиморфное λ-исчисление с конструкторами типов.
Парадокс
Hurkens A.J.C. A simplification of Girard’s paradox // Typed Lambda Calculi and Applications / ed. Dezani-Ciancaglini M., Plotkin G. Berlin, Heidelberg: Springer Berlin Heidelberg, 1995. Vol. 902. P. 266–278.
2S ≡ Pow ≡ λ (S : Type). Π S . Type
Univ ≡ Π (X : Type). (Π (Π 22X . X). 22X)
PPUniv ≡ 22Univ
τ ≡ λ (t : PPUniv) (X : Type) (f : Π 22X . X ) (p : 2X ) . t · (λ (x : Univ) . (p (f ((x X) f))))
σ ≡ λ (s : Univ) . ((s · Univ) (λ (t : PPUniv) . τ · t))
Δ ≡ λ (y : Univ) . (Π (Π (p : (Pow Univ)). (σ y p) (p (τ (σ y)))) ⊥)
Ω ≡ (τ · (λ (p : 2Univ)) · (Π (x : Univ). (σ · x · p) · (p · x)))
False ≡ (λ (O : (Π (p : 2Univ) (Π (x : Univ) . (σ · x · p) · (p · x)) (p · Ω) )). (((O Δ) (λ (x : Univ) (t : σ x Δ) (u : (Π (p : (Pow Univ)). (σ y p) (p (τ (σ y))))). (u · (λ (y : Univ)) (p (τ (σ y))))))) · (λ (p : 2Univ) . (O (λ (y : Univ) . p (τ (σ y))))) · (λ (p : 2Univ). (v : (Π (x : Univ). (σ x p) (p x))). (v · Ω) · (λ (x : Univ) . (v · (τ (σ x))))))
False : ⊥
Терм False не нормализуем.
Уровни универсумов
Для того, чтобы избежать таких парадоксов, рассматривают не один универсум Type : Type, а их набор Typei, где i — какой-то набор индексов с заданным строгим порядком <. Обычно используется что-то вроде решётки, чтобы у любой пары индексов был максимальный индекс max, нестрого больший i, j ⩽ max(i,j).
Тогда правило типизации универсума преобразуется к следующему виду:
i < j |
---|
Typei : Typej |
А правила типизации зависимого произведения — к виду:
Γ, x : α ⊦ A : Typei Γ ⊦ α : Typej |
---|
Γ ⊦ Π (x : α). A : Typemax(i,j) |
Уровни универсумов
При проверке типов индексы строятся в виде ориентированного графа, рёбра которого помечены < и ⩽, а потом, например, алгоритмом Тарьяна, определяется, можно ли получить на основе этого графа требуемый порядок (можно, если нет ориентированного цикла с одним из рёбер, помеченных <).
Luo Z. ECC, an extended calculus of constructions // [1989] Proceedings. Fourth Annual Symposium on Logic in Computer Science. IEEE Comput. Soc. Press, 1989. P. 386–395.
Но в итоге всё равно получается, что объявления обобщённых типов это, фактически, схемы типов, полиморфные по индексам типов:
List T ≡ λ(T : Typeᵢ). Π(R : Typeⱼ). R → (T → R → R) → R
List : Π(T : Typeᵢ).Typeₖ, k > j.
Массив с контролем границ
Длина массива — неотрицательное целое число, это не то же самое, что int. Далее обозначаем ℕ (в Coq — nat, встроенный тип натуральных чисел с нулём).
S : ℕ → ℕ
· < · : ℕ → ℕ → Type
succ_lt : Π (n : ℕ). n < S n
Array : Type → ℕ → Type
Функция получения элемента: get : Π (T : Type). (n : ℕ). (a : Array T n). (i : ℕ). (i < n) → T
Массивы зависимой длины: λ (n : ℕ). (d : Array double (S n)). (c e : Array double n). (a b x : Array n). …
Зависимая сумма (пара «терм — свойство»)
Зависимое произведение Π (x : A). B обобщает тип функций A → B таким образом, что тип B зависит от значения X.
Аналогично, зависимая сумма Σ (x : A). B обобщает тип пар A * B:
Γ ⊦ A : Typei Γ,A ⊦ B : Typej |
---|
Γ ⊦ Σ(x : A).B : Typemax(i,j) |
Γ ⊦ Σ(x : A).B : Typek Γ ⊦ a : A Γ ⊦ b : B[x := a] |
---|
Γ ⊦ pairΣA.B(a, b) : Σ(x:A).B |
Γ ⊦ a : Σ(x:A).B Γ, s : Σ(x:A).B ⊦ C : Type Γ, a : A, b : B ⊦ r : C · pair(a, b) |
---|
Γ ⊦ split(C, s, r) : C · s |
split — пример терма зависимого удаления. Он выразим через fst, snd только в исчислении с η-расширением, а для такого исчисления на настоящее время нет доказательства сильной нормализации.
Функция, которая возвращает массив, имеющий длину не менее заданной: Π (n : ℕ) . Σ (m : ℕ) (p : n < m). (b : Array double m)
Функция, которая факторизует число: Π (n : ℕ). Σ (m : ℕ). (Eq 0 (mod n m))
Разрешимые свойства
В некоторых случаях задавать какие-то ограничения на этапе компиляции затруднительно, лучше принимать решение в процессе исполнения.
Сравнение a < b
можно представить в более
типизированном и менее типизированном виде:
Менее типизированный: compare(a, b) : Bool
Более типизированный (разрешение свойства a<b
):
compare(a, b) : {a < b} + { b ⩽ a }
В последнем случае оператор удаления case
может передать
полученный тип далее. В языках с зависимыми типами использовать «менее
типизированный» способ разрешения свойств почти никогда не имеет
смысла.
Прогонка в λΠω
Что осталось непроверенным
- корректная последовательность доступа к элементам массива
- корректность результата (после обработки для матрицы с диагональным преобладанием в массиве x должно быть решение исходной системы линейных уравнений).
Соответствие Карри-Говарда
λΠ (λ-исчисление с простыми зависимыми типами)
Типы могут зависеть от значений; нет полиморфизма или конструкторов типов.
Можно связать с конструктивной логикой первого порядка (?).
λΠω (исчисление конструкций)
Можно связать с конструктивным исчислением предикатов второго порядка (?).
Есть пример, демонстрирующий эквивалентность исчисления конструкций и аксиоматики IZF (интуиционистская аксиоматика Цермело-Френкеля):
Литература
- Б. Пирс. Типы в языках программирования. 2010
- Henk Barendregt, Wil Dekkers, Richard Statman. Lambda Calculus With Types. Cambridge University Press, 2010.
- М. Кривчиков. Формальные модели и верификация свойств программ с использованием промежуточного представления.
Глава 1, раздел 2.1, подраздел 3.2.6.
https://istina.msu.ru/dissertations/10283583/
https://istina.msu.ru/download/10295218/1kk5zk:mNYCzzLrvKtV3M6NcjnCEr_L2tI/
Задачи
- Задача 7.1 *
- Записать тип равенства, его свойства и их доказательства в Coq так, чтобы они прошли проверку типов.
- Задача 7.2 **
- Записать парадокс, вызванный циклической иерархией универсумов, в Coq и убедиться, что он не проходит проверку типов. Тип ⊥ в Coq обозначается False.
- Задача 7.3 **
- Записать в Coq (или в термах исчисления конструкций) тип функции, реализующей метод прогонки, с условием корректности ответа.
Задачи
- Задача 7.4 ***
- Предложить способ описания в Coq (или в термах исчисления конструкций) спецификации неинициализированного массива:
Array : Type → ℕ → ?? → Type
new : Π (T : Type) (n : ℕ) . Array T n (все элементы не инициализированы)
get : Π (T : Type) (n : ℕ) (? : ??) (a : Array T n) (i : ℕ). (i < n) → (элемент i инициализирован) → T
set : Π (T : Type) (n : ℕ) (? : ??) (a : Array T n) (i : ℕ) (t : T). (i < n) → Array T n (? + элемент i инициализирован)