"Какую часть Хиндли-Милнера вы не понимаете?"
Клянусь, когда-то была футболка для продажи с бессмертными словами:
Какая часть
ты не понимаешь?
В моем случае ответ будет... все это!
В частности, я часто вижу такие обозначения в документах Haskell, но я не знаю, что это значит. Я понятия не имею, какой отраслью математики она должна быть.
Я понимаю буквы греческого алфавита, конечно, и символы, такие как "∉" (что обычно означает, что что-то не является элементом набора).
С другой стороны, я никогда раньше не видел "⊢" (Wikipedia утверждает, что это может означать "раздел"). Я также не знаком с использованием винкулума здесь. (Обычно это обозначает дробь, но, похоже, это не так).
Если бы кто-нибудь мог хотя бы сказать мне, где начать понимать, что означает это море символов, это было бы полезно.
Ответы
Ответ 1
- Горизонтальная полоса означает, что "[выше] означает [ниже]".
- Если в [выше] есть несколько выражений, считайте их и вместе; все [выше] должно быть верно, чтобы гарантировать [ниже].
:
означает, что имеет тип
∈
означает, что находится в. (Точно так же ∉
означает "не в".)
Γ
обычно используется для ссылки на среду или контекст; в этом случае его можно рассматривать как набор аннотаций типов, связывающих идентификатор с его типом. Поэтому x : σ ∈ Γ
означает, что среда Γ
включает в себя тот факт, что x
имеет тип σ
.
⊢
можно прочитать как доказывает или определяет. Γ ⊢ x : σ
означает, что среда Γ
определяет, что x
имеет тип σ
.
,
- это способ включения определенных дополнительных допущений в среду Γ
.
Следовательно, Γ, x : τ ⊢ e : τ'
означает, что среда Γ
, с дополнительным, преобладающим допущением, что x
имеет тип τ
, доказывает, что e
имеет тип τ'
.
По запросу: приоритет оператора, от наивысшего к низшему:
- Специфичные для языка операторы инфикса и миксфикса, такие как
λ x . e
, ∀ α . σ
и τ → τ'
, let x = e0 in e1
и пробельные символы для применения функций.
:
∈
и ∉
,
(левый-ассоциативный)
⊢
- пробел, разделяющий несколько предложений (ассоциативный)
- турник
Ответ 2
Этот синтаксис, хотя и может показаться сложным, на самом деле довольно прост. Основная идея исходит из формальной логики: все выражение является следствием, причем верхняя половина - это предположения, а нижняя - результат. То есть, если вы знаете, что верхние выражения верны, вы можете сделать вывод, что нижние выражения также верны.
Символы
Следует также помнить, что некоторые буквы имеют традиционное значение; в частности, Γ представляет "контекст", в котором вы находитесь, то есть типы других вещей, которые вы видели. Итак, что-то вроде Γ ⊢ ...
означает "выражение ...
, когда вы знаете типы каждого выражения в Γ
.
Символ ⊢
по сути означает, что вы можете что-то доказать. Итак, Γ ⊢ ...
- это утверждение, говорящее "Я могу доказать ...
в контексте Γ
. Эти утверждения также называются суждениями типа".
Следует помнить еще одну вещь: в математике, как и в ML и Scala, x : σ
означает, что x
имеет тип σ
. Вы можете прочитать это так же, как Haskell x :: σ
.
Что означает каждое правило
Итак, зная это, первое выражение становится легко понять: если мы знаем, что x : σ ∈ Γ
(то есть x
имеет некоторый тип σ
в некотором контексте Γ
), то мы знаем, что Γ ⊢ x : σ
(что в Γ
, x
имеет тип σ
). Так что на самом деле, это не говорит вам ничего супер-интересного; он просто говорит вам, как использовать ваш контекст.
Другие правила также просты. Например, возьмите [App]
. Это правило имеет два условия: e₀
- это функция от некоторого типа τ
до некоторого типа τ'
, а e₁
- это значение типа τ
. Теперь вы знаете, какой тип вы получите, применив e₀
к e₁
! Надеюсь, это не удивительно :).
Следующее правило имеет еще один новый синтаксис. В частности, Γ, x : τ
просто означает контекст, состоящий из Γ
и суждения x : τ
. Итак, если мы знаем, что переменная x
имеет тип τ
, а выражение e
имеет тип τ'
, мы также знаем тип функции, которая принимает x
и возвращает e
. Это просто говорит нам, что делать, если мы выяснили, какой тип принимает функция и какой тип она возвращает, поэтому это тоже не должно удивлять.
Следующий просто говорит вам, как обрабатывать операторы let
. Если вы знаете, что какое-то выражение e₁
имеет тип τ
, пока x
имеет тип σ
, то выражение let
, которое локально связывает x
со значением типа σ
, сделает e₁
имеет тип τ
. На самом деле, это просто говорит вам, что оператор let по сути позволяет вам расширять контекст с помощью новой привязки - это именно то, что делает let
!
Правило [Inst]
имеет дело с подтипом. В нем говорится, что если у вас есть значение типа σ'
и это подтип σ
(⊑
представляет отношение частичного упорядочения), то это выражение также имеет тип σ
.
Последнее правило касается обобщающих типов. Небольшое отступление: свободная переменная - это переменная, которая не вводится оператором let или лямбда-выражением внутри некоторого выражения; это выражение теперь зависит от значения свободной переменной из ее контекста. Правило гласит, что если есть некоторая переменная α
, которая не является "свободной" ни в чем в вашем контексте, то можно с уверенностью сказать, что любое выражение, чье тип, который вы знаете e : σ
будет иметь этот тип для любого значения α
.
Как использовать правила
Итак, теперь, когда вы понимаете символы, что вы делаете с этими правилами? Ну, вы можете использовать эти правила, чтобы выяснить тип различных значений. Для этого посмотрите на свое выражение (скажем, f x y
) и найдите правило, в котором есть заключение (нижняя часть), соответствующее вашему утверждению. Давайте назовем то, что вы пытаетесь найти своей "целью". В этом случае вы посмотрите на правило, которое заканчивается в e₀ e₁
. Когда вы нашли это, теперь вы должны найти правила, доказывающие все, что находится выше линии этого правила. Эти вещи, как правило, соответствуют типам подвыражений, так что вы по существу повторяете части выражения. Вы просто делаете это, пока не закончите свое дерево доказательств, которое дает вам подтверждение типа вашего выражения.
Таким образом, все эти правила - это точно указать - и в обычной математически педантичной детали: P - как выяснить типы выражений.
Теперь, это должно звучать знакомо, если вы когда-либо использовали Пролог - вы по сути вычисляете дерево доказательств, как человеческий интерпретатор Пролога. Есть причина, по которой Пролог называют "логическим программированием"! Это также важно, так как я впервые познакомился с алгоритмом логического вывода H-M, реализовав его в Прологе. Это на самом деле удивительно просто и проясняет ситуацию. Вам непременно стоит попробовать.
Примечание: я, вероятно, допустил некоторые ошибки в этом объяснении и был бы рад, если бы кто-то на них указал. На самом деле я расскажу об этом в классе через пару недель, поэтому я буду более уверенным в этом: P.
Ответ 3
если бы кто-нибудь мог хотя бы сказать мне, где начать искать, чтобы понять, что означает это море символов
См. "Практические основы языков программирования.", главы 2 и 3, о стиле логики посредством суждений и выводов. Вся книга теперь доступна на Amazon.
Глава 2
Индуктивные определения
Индуктивные определения являются незаменимым инструментом в изучении языков программирования. В этой главе мы разработаем основные рамки индуктивных определений и приведем несколько примеров их использования. Индуктивное определение состоит из набора правил для выведения суждений или утверждений различных форм. Суждения - это утверждения об одном или нескольких синтаксических объектах указанного вида. Правила определяют необходимые и достаточные условия для действительности решения и, следовательно, полностью определяют его значение.
2.1 Суждения
Мы начнем с понятия суждения или утверждения о синтаксическом объекте. Мы будем использовать многие формы суждений, включая такие примеры:
- n nat - n натуральное число
- n = n1 + n2 - n это сумма n1 и n2
- τ тип - τ это тип
- e: τ - выражение e имеет тип τ
- e ⇓ v - выражение e имеет значение v
Суждение гласит, что один или несколько синтаксических объектов имеют свойство или стоят в некотором отношении друг к другу. Само свойство или отношение называется формой суждения, а суждение о том, что объект или объекты обладают этим свойством или позицией в этом отношении, называется экземпляром этой формы суждения. Форма суждения также называется предикатом, а объекты, составляющие экземпляр, являются его субъектами. Мы пишем J для суждения, утверждающего, что J держит a. Когда не важно подчеркнуть предмет суждения (текст обрывается здесь)
Ответ 4
Обозначение происходит из естественной дедукции.
⊢ символ называется turnstile.
6 правил очень просты.
Правило Var
- довольно тривиальное правило - оно говорит, что если тип для идентификатора уже присутствует в вашей среде типа, то выведите тип, который вы просто берете из среды, как есть.
Правило App
гласит, что если у вас есть два идентификатора e0
и e1
и вы можете вывести их типы, вы можете сделать вывод о типе приложения e0 e1
. Правило читается так, если вы знаете, что e0 :: t0 -> t1
и e1 :: t0
(то же t0!), То приложение хорошо напечатано и тип t1
.
Abs
и Let
- правила для вывода типов для лямбда-абстракции и впуска.
Inst
правило говорит, что вы можете заменить тип менее общим.
Ответ 5
Как я понимаю правила Хиндли-Милнера?
Хиндли-Милнер - это набор правил в форме последовательного исчисления (не естественное вычитание), который демонстрирует, что мы можем вывести (наиболее общий) тип программы из конструкции программы без явных объявлений типов.
Символы и обозначения
Во-первых, давайте объясним символы и обсудим приоритет операторов
- 𝑥 - это идентификатор (неофициально, имя переменной).
- : означает тип (неофициально, экземпляр или "is-a").
- 𝜎 (сигма) - это выражение, которое является либо переменной, либо функцией.
- таким образом, 𝑥: 𝜎 читается как "𝑥 is-a 𝜎"
- ∈ означает "является элементом"
- 𝚪 (Гамма) - это среда.
- ⊦ (знак утверждения) означает, что утверждает (или доказывает, но контекстно "утверждает" читается лучше.)
- 𝚪 ⊦ 𝑥 : 𝜎, таким образом, читается "𝚪 утверждает, что 𝑥, является 𝜎"
- 𝑒 - это фактический экземпляр (элемент) типа 𝜎.
- 𝜏 (tau) - это тип: основной, переменный (𝛼), функциональный 𝜏 → 𝜏 ' или продукт 𝜏 × 𝜏' ( продукт здесь не используется)
- 𝜏 → 𝜏 ' - это функциональный тип, где 𝜏 и 𝜏' потенциально разные типы.
𝜆𝑥.𝑒 означает, что 𝜆 (лямбда) - это анонимная функция, которая принимает аргумент 𝑥 и возвращает выражение 𝑒.
пусть 𝑥 =𝑒₀ в 𝑒₁ означает в выражении 𝑒₁, заменяйте 𝑒₀ везде, где появляется 𝑥.
⊑ означает, что предыдущий элемент является подтипом (неофициально - подклассом) последнего элемента.
- 𝛼 является переменной типа.
- ∀ 𝛼.𝜎 - это тип, argument (для всех) переменных аргумента, 𝛼, возвращающее 𝜎 выражение
- free (𝚪) означает не элемент переменных свободного типа из 𝚪, определенных во внешнем контексте. (Связанные переменные являются заменяемыми.)
Все выше линии - предпосылка, все ниже - заключение (Per Martin-Löf)
Приоритет, на примере
Я взял некоторые из более сложных примеров из правил и вставил лишние скобки, которые показывают приоритет:
- 𝑥: 𝜎 ∈ 𝚪 можно написать (𝑥: 𝜎) ∈ 𝚪
𝚪 ⊦ 𝑥 : 𝜎 можно написать 𝚪 ⊦ (𝑥 : 𝜎)
𝚪 ⊦ let 𝑥 =𝑒₀ in 𝑒₁: 𝜏
эквивалентно
𝚪 ⊦ ((let (𝑥 =𝑒₀) в 𝑒₁): 𝜏)
𝚪 ⊦ 𝜆𝑥.𝑒: 𝜏 → 𝜏 ' эквивалентно 𝚪 ⊦ ((𝜆𝑥.𝑒): (𝜏 → 𝜏'))
Затем большие пробелы, отделяющие операторы утверждений и другие предварительные условия, указывают на набор таких предварительных условий, и, наконец, горизонтальная линия, отделяющая предпосылку от заключения, приводит к концу порядка предшествования.
Правила
Ниже следует толкование правил на английском языке, каждое из которых сопровождается свободной переформулировкой и пояснением.
Переменная
Данный 𝑥 является типом 𝜎 (сигма), элементом 𝚪 (гамма),
сделать вывод 𝚪 утверждает 𝚪 является <.
Другими словами, в 𝚪 мы знаем, что 𝑥 имеет тип 𝜎, потому что 𝑥 имеет тип 𝜎 в <.
Это в основном тавтология. Имя идентификатора - это переменная или функция.
Применение функции
Дано 𝚪 утверждает 𝑒₀ является функциональным типом, а 𝚪 утверждает 𝑒₁ является 𝜏
В заключение 𝚪 утверждает, что применяет функцию 𝑒₀ к 𝑒₁ типу applying '
Чтобы переформулировать правило, мы знаем, что приложение функции возвращает тип 𝜏 ', потому что функция имеет тип 𝜏 → 𝜏' и получает аргумент типа 𝜏.
Это означает, что если мы знаем, что функция возвращает тип, и мы применяем его к аргументу, результатом будет экземпляр возвращаемого нами типа.
Абстракция функций
Given 𝚪 и 𝑥 of type 𝜏 asserts 𝑒 is a type, 𝜏'
conclude 𝚪 asserts an anonymous function, 𝜆 of 𝑥 returning expression, 𝑒 is of type 𝜏→𝜏'.
Опять же, когда мы видим функцию, которая принимает 𝑥 и возвращает выражение 𝑒, мы знаем, что она имеет тип 𝜏 → 𝜏 ', потому что 𝑥 (a 𝜏) утверждает, что 𝑒 является 𝜏'.
Если мы знаем, что 𝑥 имеет тип 𝜏 и, следовательно, выражение 𝑒 имеет тип 𝜏 ', то функция a, возвращающая выражение 𝑒, имеет тип 𝜏 → 𝜏'.
Пусть объявление переменной
Given 𝚪 asserts 𝑒₀, of type 𝜎, and 𝚪 и 𝑥, of type 𝜎, asserts 𝑒₁ of type 𝜏
conclude 𝚪 asserts let
𝑥=𝑒₀ in
𝑒₁ of type 𝜏
Скорее всего, 𝑥 связан с 𝑒₀ в 𝑒₁ (a 𝜏), потому что a - это 𝜎, а 𝑥 - это 𝜎, который утверждает, что 𝑒₁ - это <.
Это означает, что если у нас есть выражение 𝑒₀, представляющее собой 𝜎 (являющееся переменной или функцией), и некоторое имя 𝑥, также a a и выражение 𝑒₁ типа 𝜏, то мы можем заменить 𝑒₀ на 𝑥, где бы оно ни находилось внутри 𝑒₁.
Конкретизация
Дано 𝚪 утверждает 𝑒 типа 𝜎 ', а 𝜎' является подтипом 𝜎
сделать вывод 𝚪 утверждает 𝚪 типа ser
Выражение 𝑒 имеет родительский тип 𝜎, поскольку выражение 𝑒 является подтипом 𝜎 ', а 𝜎 является родительским типом 𝜎'.
Если экземпляр имеет тип, который является подтипом другого типа, то он также является экземпляром этого супертипа - более общего типа.
Обобщение
С учетом того, что ser asserts 𝜎 является и , а 𝛼 не является элементом свободных переменных 𝚪,
conclude 𝚪 asserts 𝑒, введите для всех выражений аргумента 𝛼 возвращающее 𝜎 выражение
Таким образом, в общем случае, 𝑒 типизирован 𝜎 для всех переменных аргумента (𝛼), возвращающих 𝜎, потому что мы знаем, что 𝑒 является 𝜎, а a не является свободной переменной.
Это означает, что мы можем обобщить программу для принятия всех типов для аргументов, которые еще не связаны в содержащей области (переменные, которые не являются нелокальными). Эти связанные переменные являются заменяемыми.
Собираем все вместе
Учитывая определенные предположения (такие как отсутствие свободных/неопределенных переменных, известная среда), мы знаем типы:
- атомарные элементы наших программ (переменные),
- значения, возвращаемые функциями (Application Function),
- функциональные конструкции (Function Abstraction),
- пусть привязки (объявления переменных),
- родительские типы экземпляров (Instantiation) и
- все выражения (обобщение).
Заключение
Объединение этих правил позволяет нам доказать наиболее общий тип утвержденной программы, не требуя аннотаций типов.
Ответ 6
Я полагаю, что SO не является хорошим местом для объяснения всего алгоритма Милнера Хиндли.
Если вы ищете хорошее объяснение алгоритма, лучшее, что я нашел до сих пор, находится в главе 30 Шрирама Кришнамурти Языки программирования: приложение и интерпретация (лицензия CC!). Вот одна веская причина, почему это хорошее объяснение: примеры!
Ответ 7
Есть два способа думать о e: σ. Один из них: "выражение e имеет тип σ", другое - "упорядоченная пара выражения e и тип σ".
Просмотр Γ как знания о типах выражений, реализованных как набор пар выражения и типа, e: σ.
Турникет ⊢ означает, что из знания слева, мы можем вывести, что справа.
Таким образом, первое правило [Var] можно прочитать:
Если наше знание Γ содержит пару e: σ, то из Γ можно вывести, что e имеет тип σ.
Второе правило [App] можно прочитать:
Если из Γ можно вывести, что e_0 имеет тип τ → τ ', а из Γ можно вывести, что e_1 имеет тип τ, то из Γ можно вывести, что e_0 e_1 имеет тип τ'.
Общепринято писать Γ, e: σ вместо Γ ∪ {e: σ}.
Таким образом, можно прочитать третье правило [Abs]:
Если мы из Γ, расширенной с x: τ, можем вывести, что e имеет тип τ ', то из Γ можно вывести, что λx.e имеет тип τ → τ'.
Четвертое правило [Let] остается упражнением.: -)
Пятое правило [Inst] можно прочитать:
Если мы из Г можем вывести, что e имеет тип σ ', а σ' - подтип σ, то из Γ можно вывести, что e имеет тип σ.
Шестое и последнее правило [Gen] можно прочитать:
Если мы из Γ можем вывести, что e имеет тип σ, а α не является переменной свободного типа в любом из типов в Γ, то из Γ можно вывести, что e имеет тип ∀α σ.