Недавно я закончил университетский курс, посвященный Haskell и Agda (зависимый типизированный функциональный язык программирования), и мне было интересно, можно ли заменить в них лямбда-исчисление комбинаторной логикой. В Haskell это кажется возможным с использованием комбинаторов S и K, что делает его бессмысленным. Мне было интересно, что было эквивалентом для Агды. То есть, можно ли сделать язык функционального программирования с зависимой типизацией эквивалентным Agda без использования каких-либо переменных?
Кроме того, можно ли как-то заменить количественную оценку комбинаторами? Я не знаю, совпадение ли это, но универсальная количественная оценка, например, делает сигнатуру типа похожей на лямбда-выражение. Есть ли способ удалить универсальную количественную оценку из сигнатуры типа без изменения ее значения? Например, в:
forall a : Int -> a < 0 -> a + a < a
Можно ли выразить то же самое без использования forall?
Ответы:
Так что я подумал об этом еще немного и добился некоторого прогресса. Вот первая попытка кодирования восхитительно простой (но непоследовательной)
Set : Set
системы Мартина-Лёфа в комбинаторном стиле. Это не лучший способ закончить, но это самый простой способ начать. Синтаксис этой теории типов - это просто лямбда-исчисление с аннотациями типов, Pi-типами и набором юниверсов.Теория целевого типа
Для полноты картины приведу правила. Правильность контекста просто говорит о том, что вы можете создавать контексты из пустых, добавляя новые переменные, населяющие
Set
s.G |- valid G |- S : Set -------------- ----------------------------- x fresh for G . |- 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 -------------- x:S in G ----------------------------- S ={beta} T 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 -- requires first arg in normal form C S :. f :. a $. g = f $. g $. (a :. g) -- S f a g = f g (a g) share environment C K :. a $. g = a -- K a g = a drop environment n $. g = n :. norm g -- guarantees output in normal form infixl 4 $.
(Упражнение для читателя состоит в том, чтобы определить тип именно для нормальных форм и уточнить типы этих операций.)
Представление теории типов
Теперь мы можем определить синтаксис нашей теории типов.
data Tm a = Var a | Lam (Tm a) (Tm (Su a)) -- Lam is the only place where binding happens | Tm a :$ Tm a | Pi (Tm a) (Tm a) -- the second arg of Pi is a function computing a Set | 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 -- binds a variable, building a function bra (V Ze) = C S :. C K :. C K -- the variable itself yields the identity bra (V (Su x)) = C K :. V x -- free variables become constants bra (C c) = C K :. C c -- combinators become constant bra (f :. a) = C S :. bra f :. bra a -- S is exactly lifted application
Набор комбинаторов
Перевод показывает, как мы используем комбинаторы, что дает нам представление о том, какими должны быть их типы.
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 ----------------- A ={norm} B 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
наборов итераций, используемых для переменных. В каждом таком наборе есть сохраняющий конструкторemb
edding в приведенный выше набор плюс новый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 -- Ze is the only, so the highest emb = magic embd _ = Nothing -- there was nothing to embed instance Fin x => Fin (Su x) where top = Su top -- the highest is one higher emb Ze = Ze -- emb preserves Ze emb (Su x) = Su (emb x) -- and Su embd Ze = Just Ze -- Ze is definitely embedded embd (Su x) = fmap Su (embd x) -- otherwise, wait and see
Теперь я могу определить «меньше или меньше» с помощью операции ослабления .
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 -- nothing to embed instance Le x y => Le (Su x) (Su y) where wk x = case embd x of Nothing -> top -- top maps to top Just y -> emb (wk y) -- embedded gets weakened and embedded
И как только вы разберетесь с этим, остальное сделает небольшая уловка.
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 достаточно информации для вычисления сдвига, необходимого для перевода в представление де Брейна. Зачем держать собаку и лаять самому?
источник
F
комбинатор?F
действует по-разному в зависимости от своего первого аргумента: еслиA
является атомом,M
аN
являются термами иPQ
является составной частью, тоFAMN -> M
иF(PQ)MN -> NPQ
. Это не может быть представлено вSK(I)
расчетах, ноK
может быть представлено какFF
. Можно ли с помощью этого продлить ваш MLTT без баллов?λ (A : Set) → λ (a : A) → a
типа , который не может использоваться в типе, где тип второго аргумента зависит от первого аргумента.(A : Set) → (a : A) → A
S(S(KS)(KK))(KK)
Я предполагаю, что «абстракция скобок» работает также и для зависимых типов при некоторых обстоятельствах. В разделе 5 следующей статьи вы найдете несколько типов K и S:
Возмутительные, но значащие совпадения
Синтаксис и оценка, безопасные для типов,
Конор МакБрайд, Университет Стратклайда, 2010 г.
Преобразование лямбда-выражения в комбинаторное выражение примерно соответствует преобразованию доказательства естественного вывода в доказательство в стиле Гильберта.
источник