Тож я трохи більше подумав про це і домігся певного прогресу. Ось перший удар у кодуванні чудово простої (але суперечливої) Set : Set
системи Мартіна-Лефа в поєднанному стилі. Це не найкращий спосіб закінчити, але це найпростіше місце для початку. Синтаксис цієї теорії типів - це просто лямбда-числення з анотаціями типів, Pi-типами та набором всесвіту.
Теорія типу цілей
Для повноти викладу правила. Валідність контексту просто говорить, що ви можете будувати контексти з порожнього, приєднуючись до нових змінних, що населяють Set
s.
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 та лямбда, щоб уникнути повторення типу домену, і досить корисно дозволяє мені використовувати функціональний простір Хаскелла для прив'язки змінних. Можливо, ви майже можете прочитати наступне!
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)
Визначивши їх, я витягнув відповідні відкриті субтерми та провів їх через переклад.
Набір інструментів кодування de Bruijn
Ось як будувати pil
. По-перше, я визначаю клас Fin
наборів ite, що використовується для змінних. Кожен такий набір має emb
редагування, що зберігає конструктор, у набір вище, а також новий 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 достатньо інформації для обчислення зміщення, необхідного для перекладу, до подання де Бруйна. Навіщо тримати собаку і гавкати самому?