Так что я подумал об этом еще немного и добился некоторого прогресса. Вот первая попытка кодирования восхитительно простой (но непоследовательной) Set : Setсистемы Мартина-Лёфа в комбинаторном стиле. Это не лучший способ закончить, но это самый простой способ начать. Синтаксис этой теории типов - это просто лямбда-исчисление с аннотациями типов, Pi-типами и набором юниверсов.
Теория целевого типа
Для полноты картины приведу правила. Правильность контекста просто говорит о том, что вы можете создавать контексты из пустых, добавляя новые переменные, населяющие Sets.
G |- valid G |- S : Set
. |- valid G, x:S |- valid
И теперь мы можем сказать, как синтезировать типы для терминов в любом заданном контексте и как изменить тип чего-либо, вплоть до вычислительного поведения содержащихся в нем терминов.
G |- valid G |- S : Set G |- T : Pi S \ x:S -> Set
G |- Set : Set G |- Pi S T : Set
G |- S : Set G, x:S |- t : T x G |- f : Pi S T G |- s : S
G |- \ x:S -> t : Pi S T G |- f s : T s
G |- valid G |- s : S G |- T : Set
G |- x : S G |- s : T
В небольшом отклонении от оригинала я сделал лямбда единственным оператором привязки, поэтому вторым аргументом Pi должна быть функция, вычисляющая, как тип возвращаемого значения зависит от ввода. По соглашению (например, в Agda, но, к сожалению, не в Haskell), область действия лямбда расширяется вправо, насколько это возможно, поэтому вы можете часто оставлять абстракции без скобок, когда они являются последним аргументом оператора высшего порядка: вы можете видеть, что я сделал что с Пи. Ваш тип Agda (x : S) -> Tстанет Pi S \ x:S -> T.
( Отступление . Аннотации типов на лямбда-выражениях необходимы, если вы хотите иметь возможность синтезировать тип абстракций. Если вы переключитесь на проверку типов в качестве своего метода работы, вам все равно потребуются аннотации для проверки бета-редекса (\ x -> t) s, поскольку у вас нет возможности угадывать типы частей от целого. Я советую современным дизайнерам проверять типы и исключать бета-редексы из самого синтаксиса.)
( Отступление . Эта система несовместима, поскольку Set:Setпозволяет кодировать множество «парадоксов лжецов». Когда Мартин-Лёф предложил эту теорию, Жирар послал ему ее кодировку в своей собственной несовместимой Системе U. Последующий парадокс, связанный с Херкенсом, заключается в самая опрятная токсичная конструкция, которую мы знаем.)
Синтаксис комбинатора и нормализация
Во всяком случае, у нас есть два дополнительных символа, Pi и Set, так что, возможно, мы могли бы организовать комбинаторный перевод с S, K и двумя дополнительными символами: я выбрал U для вселенной и P для продукта.
Теперь мы можем определить нетипизированный комбинаторный синтаксис (со свободными переменными):
data SKUP = S | K | U | P deriving (Show, Eq)
data Unty a
= C SKUP
| Unty a :. Unty a
| V a
deriving (Functor, Eq)
infixl 4 :.
Обратите внимание, что я включил aв этот синтаксис средства для включения свободных переменных, представленных типом . Помимо рефлекса с моей стороны (каждый синтаксис, достойный названия, представляет собой бесплатную монаду с returnвстраиваемыми переменными и >>=выполняемой заменой), будет удобно представлять промежуточные этапы в процессе преобразования терминов с привязкой к их комбинаторной форме.
Вот нормализация:
norm :: Unty a -> Unty a
norm (f :. a) = norm f $. a
norm c = c
($.) :: Unty a -> Unty a -> Unty a
C S :. f :. a $. g = f $. g $. (a :. g)
C K :. a $. g = a
n $. g = n :. norm g
infixl 4 $.
(Упражнение для читателя состоит в том, чтобы определить тип именно для нормальных форм и уточнить типы этих операций.)
Представление теории типов
Теперь мы можем определить синтаксис нашей теории типов.
data Tm a
= Var a
| Lam (Tm a) (Tm (Su a))
| Tm a :$ Tm a
| Pi (Tm a) (Tm a)
| Set
deriving (Show, Functor)
infixl 4 :$
data Ze
magic :: Ze -> a
magic x = x `seq` error "Tragic!"
data Su a = Ze | Su a deriving (Show, Functor, Eq)
Я использую представление индекса де Брёйна в манере Бельгарда и Крюка (как это популяризовали Бёрд и Патерсон). Тип Su aимеет на один элемент больше a, чем , и мы используем его как тип свободных переменных Zeв связывании , где как вновь связанная переменная и Su xявляется смещенным представлением старой свободной переменной x.
Перевод терминов в комбинаторы
И после этого мы получаем обычный перевод, основанный на абстракции скобок .
tm :: Tm a -> Unty a
tm (Var a) = V a
tm (Lam _ b) = bra (tm b)
tm (f :$ a) = tm f :. tm a
tm (Pi a b) = C P :. tm a :. tm b
tm Set = C U
bra :: Unty (Su a) -> Unty a
bra (V Ze) = C S :. C K :. C K
bra (V (Su x)) = C K :. V x
bra (C c) = C K :. C c
bra (f :. a) = C S :. bra f :. bra a
Набор комбинаторов
Перевод показывает, как мы используем комбинаторы, что дает нам представление о том, какими должны быть их типы. Uи Pявляются просто конструкторами набора, поэтому, записывая непереведенные типы и разрешая "нотацию Agda" для Pi, мы должны иметь
U : Set
P : (A : Set) -> (B : (a : A) -> Set) -> Set
KКомбинатор используется , чтобы поднять значение некоторого типа Aс постоянной функцией в течение некоторого другого типа G.
G : Set A : Set
K : (a : A) -> (g : G) -> A
SКомбинатор используется для лифтовых применений по типу, при котором все части могут зависеть.
G : Set
A : (g : G) -> Set
B : (g : G) -> (a : A g) -> Set
S : (f : (g : G) -> (a : A g) -> B g a ) ->
(a : (g : G) -> A g ) ->
(g : G) -> B g (a g)
Если вы посмотрите на тип S, вы увидите, что он точно определяет контекстуализированное правило приложения теории типов, так что именно это делает его подходящим для отражения конструкции приложения. Это его работа!
Тогда у нас есть заявки только на закрытые вещи
f : Pi A B
a : A
f a : B a
Но есть загвоздка. Я написал типы комбинаторов в теории обычных типов, а не в теории комбинаторных типов. К счастью, у меня есть машина, которая сделает перевод.
Система комбинаторного типа
U : U
P : PU(S(S(KP)(S(S(KP)(SKK))(S(KK)(KU))))(S(KK)(KU)))
G : U
A : U
K : P[A](S(S(KP)(K[G]))(S(KK)(K[A])))
G : U
A : P[G](KU)
B : P[G](S(S(KP)(S(K[A])(SKK)))(S(KK)(KU)))
S : P(P[G](S(S(KP)(S(K[A])(SKK)))(S(S(KS)(S(S(KS)(S(KK)(K[B])))(S(KK)(SKK))))
(S(S(KS)(KK))(KK)))))(S(S(KP)(S(S(KP)(K[G]))(S(S(KS)(S(KK)(K[A])))
(S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KP)))(S(KK)(K[G]))))
(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(S(KS)(S(KK)(KS)))
(S(S(KS)(S(KK)(KK)))(S(KK)(K[B])))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))
(S(KK)(KK))))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(KK)(KK)))
(S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))(S(KK)(KK)))))))
M : A B : U
M : B
Вот и все, во всей нечитаемой красе: комбинаторное представление Set:Set!
Есть еще небольшая проблема. Синтаксис системы не дает вам возможности угадать G, Aи Bпараметры для Sи аналогично для K, просто по терминам. Соответственно, мы можем проверять производные ввода алгоритмически, но мы не можем просто проверять типы терминов комбинатора, как это было бы в исходной системе. Что может сработать, так это потребовать, чтобы входные данные для проверки типов содержали аннотации типов для использования S и K, эффективно записывая вывод. Но это еще одна банка с червями ...
Это хорошее место, чтобы остановиться, если вы достаточно сильно захотели начать. Остальное - «закулисная».
Генерация типов комбинаторов
Я создал эти комбинаторные типы, используя перевод абстракции скобок из соответствующих терминов теории типов. Чтобы показать, как я это сделал, и сделать этот пост не совсем бессмысленным, позвольте предложить свое оборудование.
Я могу написать типы комбинаторов, полностью абстрагированные по их параметрам, следующим образом. Я использую свою удобную pilфункцию, которая объединяет Pi и лямбда, чтобы избежать повторения типа домена, и довольно полезно позволяет мне использовать пространство функций Haskell для связывания переменных. Возможно, вы почти сможете прочитать следующее!
pTy :: Tm a
pTy = fmap magic $
pil Set $ \ _A -> pil (pil _A $ \ _ -> Set) $ \ _B -> Set
kTy :: Tm a
kTy = fmap magic $
pil Set $ \ _G -> pil Set $ \ _A -> pil _A $ \ a -> pil _G $ \ g -> _A
sTy :: Tm a
sTy = fmap magic $
pil Set $ \ _G ->
pil (pil _G $ \ g -> Set) $ \ _A ->
pil (pil _G $ \ g -> pil (_A :$ g) $ \ _ -> Set) $ \ _B ->
pil (pil _G $ \ g -> pil (_A :$ g) $ \ a -> _B :$ g :$ a) $ \ f ->
pil (pil _G $ \ g -> _A :$ g) $ \ a ->
pil _G $ \ g -> _B :$ g :$ (a :$ g)
Определив их, я извлек соответствующие открытые подтермы и прогнал их по переводу.
Набор инструментов для кодирования де Брюйна
Вот как построить pil. Во-первых, я определяю класс Finнаборов итераций, используемых для переменных. В каждом таком наборе есть сохраняющий конструктор embedding в приведенный выше набор плюс новый topэлемент, и вы можете отличить их друг от друга: embdфункция сообщает вам, есть ли значение в изображении emb.
class Fin x where
top :: Su x
emb :: x -> Su x
embd :: Su x -> Maybe x
Конечно, мы можем создать экземпляр Finдля ZeиSuc
instance Fin Ze where
top = Ze
emb = magic
embd _ = Nothing
instance Fin x => Fin (Su x) where
top = Su top
emb Ze = Ze
emb (Su x) = Su (emb x)
embd Ze = Just Ze
embd (Su x) = fmap Su (embd x)
Теперь я могу определить «меньше или меньше» с помощью операции ослабления .
class (Fin x, Fin y) => Le x y where
wk :: x -> y
wkФункция должна вставлять элементы в xкачестве крупнейших элементов y, так что дополнительные вещи в yменьше, и , следовательно , в де терминов индекса Брейны, связанный более локально.
instance Fin y => Le Ze y where
wk = magic
instance Le x y => Le (Su x) (Su y) where
wk x = case embd x of
Nothing -> top
Just y -> emb (wk y)
И как только вы разберетесь с этим, остальное сделает небольшая уловка.
lam :: forall x. Tm x -> ((forall y. Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
lam s f = Lam s (f (Var (wk (Ze :: Su x))))
pil :: forall x. Tm x -> ((forall y . Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
pil s f = Pi s (lam s f)
Функция высшего порядка не просто дает вам термин, представляющий переменную, она дает вам перегруженный объект , который становится правильным представлением переменной в любой области, где переменная видна. То есть тот факт, что я пытаюсь различать различные области действия по типу, дает программе проверки типов Haskell достаточно информации для вычисления сдвига, необходимого для перевода в представление де Брейна. Зачем держать собаку и лаять самому?