Если что-то простое, то это должно быть полностью объяснимо несколькими словами. Это можно сделать для λ-исчисления:
Λ-исчисление - это синтаксическая грамматика (в основном, структура) с правилом редукции (что означает, что процедура поиска / замены неоднократно применяется к каждому вхождению определенного шаблона, пока такой шаблон не существует).
Грамматика:
Term = (Term Term) | (λ Var . Term) | Var
Правило сокращения:
((λ var body) term) -> SUBS(body,var,term) where `SUBS` replaces all occurrences of `var` by `term` in `body`, avoiding name capture.
Примеры:
(λ a . a) -> (λ a a) ((λ a . (λ b . (b a))) (λ x . x)) -> (λ b . (b (λ x x))) ((λ a . (a a)) (λ x . x)) -> (λ x . x) ((λ a . (λ b . ((b a) a))) (λ x . x)) -> (λ b . ((b (λ x . x)) (λ x . x))) ((λ x . (x x)) (λ x . (x x))) -> never halts
Хотя это и несколько неформально, можно утверждать, что это достаточно информативно для обычного человека, чтобы понять λ-исчисление в целом - и требуется 22 строки уценки. Я пытаюсь понять системы чистого / зависимого типа, используемые Idris / Agda и подобными проектами, но я могу найти более краткое объяснение: « Просто легко» - отличная статья, но, похоже, она предполагает много предыдущих знаний (Хаскель, индуктивный определения), что у меня нет. Я думаю, что что-то более короткое, менее богатое может устранить некоторые из этих барьеров. Таким образом,
Можно ли дать краткое, полное объяснение систем чистого / зависимого типа в том же формате, который я представил выше для λ-исчисления?
источник
Ответы:
отказ
Это очень неформально, как вы и просили.
Грамматика
В языке с зависимой типизацией у нас есть связующее на уровне типов, а также на уровне значений:
Хорошо введенный термин - это термин с прикрепленным типом, мы напишем
t ∈ σ
иличтобы указать, что термин
t
имеет типσ
.Правила печатания
Для простоты мы требуем, чтобы
λ v. t ∈ ∀ (v : σ). τ
обаλ
и∀
связывали одну и ту же переменную (v
в данном случае).Правила:
Таким образом,
*
это «тип всех типов» (1),∀
образует типы из типов (2), лямбда-абстракции имеют pi-типы (3) и, если ониv
представлены∀ (v : σ). τ
, тоv
имеют типσ
(5).«в нормальной форме» означает, что мы выполняем как можно больше сокращений, используя правило сокращения:
Правило сокращения
Или в двумерном синтаксисе, где
означает
t ∈ σ
:Лямбда-абстракцию можно применять только к термину, если термин имеет тот же тип, что и переменная в связанном квантификаторе поиска. Затем мы уменьшаем как лямбда-абстракцию, так и квантификатор Форалла таким же образом, как в чистом лямбда-исчислении ранее. После вычитания части уровня значения мы получаем (4) правило набора текста.
Пример
Вот оператор приложения функции:
(мы сокращаем ,
∀ (x : σ). τ
чтобыσ -> τ
еслиτ
не упоминатьx
)f
возвращаетB y
для любого предоставленногоy
типаA
. Применимf
кx
, что правильный типA
, и заменитьy
наx
в∀
AFTER.
, таким образом ,f x ∈ SUBS(B y, y, x)
~>f x ∈ B x
.Давайте теперь сокращаем оператор приложения функции как
app
и применяем его к себе:Я ставлю
?
на условия, которые мы должны предоставить. Сначала мы явно представляем и создаем экземплярA
иB
:Теперь нам нужно объединить то, что мы имеем
который так же, как
и что
app ? ?
получаетЭто приводит к
(см. также Что такое предсказуемость? )
Наше выражение (после некоторого переименования) становится
Так как для любого
A
,B
иf
(гдеf ∈ ∀ (y : A). B y
)мы можем создать экземпляр
A
иB
получить (для любогоf
с соответствующим типом)и сигнатура типа эквивалентна
(∀ (x : A). B x) -> ∀ (x : A). B x
.Все выражение
Т.е.
который после всех сокращений на уровне значения возвращает то же самое
app
.Таким образом , в то время как он требует всего несколько шагов в чистом лямбда - исчислении , чтобы получить
app
отapp app
, в типизированной настройки (и особенно в зависимости напечатал) мы также должны заботиться об объединении и вещи становятся все более сложными , даже с некоторым inconsitent удобства (* ∈ *
).Проверка типа
t
есть*
тоt ∈ *
(1)t
есть∀ (x : σ) τ
,σ ∈? *
,τ ∈? *
(смотрите примечание о∈?
ниже) , то вt ∈ *
силу (2)t
естьf x
,f ∈ ∀ (v : σ) τ
для некоторыхσ
иτ
,x ∈? σ
тоt ∈ SUBS(τ, v, x)
(4)t
является переменнойv
,v
была введена к тому∀ (v : σ). τ
времениt ∈ σ
(5)Все это правила вывода, но мы не можем сделать то же самое для лямбд (вывод типов неразрешим для зависимых типов). Поэтому для лямбд мы проверяем (
t ∈? σ
), а не выводим:t
естьλ v. b
и проверено∀ (v : σ) τ
,b ∈? τ
тоt ∈ ∀ (v : σ) τ
t
это что-то еще и проверено,σ
то определите типt
использования функции выше и проверьте, является ли этоσ
Проверка на равенство типов требует, чтобы они были в нормальных формах, поэтому, чтобы решить,
t
имеет ли тип,σ
мы сначала проверяем,σ
имеет ли тип*
. Если это так, тоσ
это нормализуемо (по парадоксу по модулю Жирара), и оно нормализуется (следовательно,σ
становится хорошо сформированным (0)).SUBS
также нормализует выражения для сохранения (0).Это называется двунаправленной проверкой типов. При этом нам не нужно аннотировать каждую лямбду с типом: если
f x
типf
известен, тоx
проверяется на соответствие типуf
получаемого аргумента, а не выводится и сравнивается на равенство (что также менее эффективно). Но еслиf
это лямбда, это требует явной аннотации типа (аннотации опущены в грамматике, и везде вы можете добавлятьAnn Term Term
илиλ' (σ : Term) (v : Var)
к конструкторам).Кроме того, посмотрите на проще, проще! Сообщение блога.
источник
(λ v. b ∈ ∀ (v : σ). τ) (t ∈ σ)
~>SUBS(b, v, t) ∈ SUBS(τ, v, t)
.(∀ (v : σ). τ) t ~> ...
, другое для значимого(λ v. b) t ~> ...
. Я бы удалил первый и превратил его в комментарий ниже.Пойдем. Я не буду беспокоиться о парадоксе Жирара, потому что он отвлекает от центральных идей. Мне нужно будет представить некоторые механизмы представления о суждениях и выводах и тому подобное.
грамматика
Грамматика имеет две взаимно определенные формы, «термины», которые являются общим понятием вещи (типы - вещи, значения - вещи), включая * (тип типов), зависимые типы функций и лямбда-абстракции, но также встраивание » Исключения »(т.е.« использует », а не« конструкции »), которые являются вложенными приложениями, в которых в конечном итоге в позиции функции находится либо переменная, либо термин, помеченный ее типом.
Правила сокращения
Операция подстановки t [e / x] заменяет каждое вхождение переменной x на исключение e, избегая захвата имени. Чтобы сформировать приложение, которое может быть сокращено, лямбда- термин должен быть аннотирован по его типу для исключения . Аннотация типа дает лямбда-абстракции своего рода «реактивность», позволяющую приложению продолжить работу. Как только мы достигнем точки, в которой больше не будет приложений, и активное t: T встраивается обратно в синтаксис термина, мы можем отбросить аннотацию типа.
Давайте расширим отношение сокращения by структурным замыканием: правила применяются везде внутри терминов и исключений, чтобы вы могли найти что-то соответствующее левой части. Напишите ↝ * для рефлексивно-транзитивного (0-или-более-шагового) замыкания ↝. В результате система восстановления в этом смысле сливается:
Контексты
Контексты - это последовательности, которые присваивают переменным типы, растущие справа, которые мы считаем «локальным» концом, говорящим нам о самых последних связанных переменных. Важным свойством контекстов является то, что всегда можно выбрать переменную, еще не использованную в контексте. Мы поддерживаем инвариант, что переменные, приписываемые типам в контексте, различны.
Суждения
Это грамматика суждений, но как их читать? Для начала, ⊢ является традиционным символом «турникета», отделяющим предположения от выводов: вы можете прочитать его неформально, как «говорит».
означает, что в данном контексте G тип T допускает термин t;
означает, что с учетом контекста G исключение e имеет тип S.
Суждения имеют интересную структуру: ноль или более входов , один или несколько предметов , ноль или более выходов .
То есть мы должны заранее предлагать типы терминов и просто проверять их, но мы синтезируем типы исключений.
Правила печатания
Я представляю их в смутном стиле Пролог, написав J -: P1; ...; Pn, чтобы указать, что суждение J выполняется, если также выполняются посылки P1-Pn. Предпосылкой будет другое решение или требование о сокращении.
Вот и все!
Только два правила не направлены на синтаксис: правило, которое гласит «вы можете уменьшить тип, прежде чем использовать его для проверки термина» и правило, которое гласит «вы можете уменьшить тип после того, как вы синтезировали его из исключения». Одной из жизнеспособных стратегий является сокращение типов до тех пор, пока вы не откроете самый верхний конструктор.
Эта система не сильно нормализуется (из-за Парадокса Жирара, который является парадоксом самообращения в стиле лжеца), но ее можно сильно нормализовать, разбивая * на «уровни вселенной», где любые значения, которые включают типы на более низких уровнях сами иметь типы на более высоких уровнях, предотвращая самоотдачу.
Эта система, однако, обладает свойством сохранения типа, в этом смысле.
Контексты могут вычисляться, позволяя вычислять содержащиеся в них термины. То есть, если суждение является действительным сейчас, вы можете вычислить его входные данные столько, сколько хотите, и его предмет за один шаг, и тогда можно будет каким-то образом вычислить его выходные данные, чтобы убедиться, что полученное суждение остается действительным. Доказательство является простой индукцией при выводе производных, учитывая слияние -> *.
Конечно, я представил здесь только функциональное ядро, но расширения могут быть весьма модульными. Вот пары.
источник
G, x:S, G' ⊢ x is S -: G' ⊬ x
?Корреспонденция Карри-Говард говорит , что существует систематическая соответствие между системами типов и системами доказательств в логике. С точки зрения программиста, вы можете изменить это следующим образом:
Видно с этой точки зрения:
Вот правила естественного вывода для логики первого порядка, используя диаграмму из записи Википедии о естественном удержании . Это в основном правила минимального лямбда-исчисления с зависимой типизацией.
Обратите внимание, что в правилах есть лямбда-термины. Они могут быть прочитаны как программы, которые создают доказательства предложений, представленных их типами (или, более кратко, мы просто говорим, что программы являются доказательствами ). Аналогичные правила сокращения, которые вы даете, могут применяться к этим лямбда-терминам.
Почему мы заботимся об этом? Ну, во-первых, потому что доказательства вполне могут оказаться полезным инструментом в программировании, и наличие языка, который может работать с доказательствами как первоклассными объектами, открывает много возможностей. Например, если у вашей функции есть предварительное условие, вместо того, чтобы записать это как комментарий, вы можете потребовать доказательства этого в качестве аргумента.
Во-вторых, потому что механизм системы типов, необходимый для обработки квантификаторов, может иметь другие применения в контексте программирования. В частности, языки с зависимой типизацией обрабатывают универсальные квантификаторы («для всех x, ...»), используя концепцию, называемую зависимыми типами функций - функцией, в которой статический тип результата может зависеть от значения времени выполнения аргумента.
Чтобы дать очень простое применение этого, я все время пишу код, который должен читать файлы Avro, которые состоят из записей с одинаковой структурой - все имеют одинаковый набор имен и типов полей. Это требует от меня либо:
Как вы можете видеть на странице учебника Avro Java , они показывают вам, как использовать библиотеку в соответствии с обоими этими подходами.
С зависимыми типами функций вы можете получить свой торт и съесть его за счет более сложной системы типов. Вы можете написать функцию, которая читает файл Avro, извлекает схему и возвращает содержимое файла в виде потока записей , статический тип которых зависит от схемы, хранящейся в файле . Компилятор мог бы перехватывать ошибки, когда я, например, пытался получить доступ к именованному полю, которое может отсутствовать в записях файлов, которые он будет обрабатывать во время выполнения. Сладкий, а?
источник