Приготовьтесь к изучению зависимых пар и singleletons!
Я собираюсь переписать систему немного, чтобы упростить ее.
Во-первых, я собираюсь сжать вашу вселенную типов от всех Haskell к гораздо более простой вселенной, состоящей из одного примитивного типа и стрелок.
infixr 0 :->
data Type = Unit | Type :-> Type
Надеюсь, вы должны уметь видеть, как расширить это с помощью более примитивных типов.
Я также удалю большую часть бит из Comb
, так как они могут быть выражены в терминах друг друга.
data Comb a where
S :: Comb ((a :-> b :-> c) :-> (a :-> b) :-> a :-> c)
K :: Comb (a :-> b :-> a)
(:$) :: Comb (a :-> b) -> Comb a -> Comb b
i = S :$ K :$ i
b = (S :$ (K :$ S)) :$ K
c = S :$ (S :$ (K :$ (S :$ (K :$ S) :$ K)) :$ S) :$ (K :$ K)
w = S :$ S :$ (S :$ K)
Теперь к вашему вопросу. Как вы правильно поняли, когда вы читаете пользовательский ввод, вы не можете заранее предсказать, какой тип результирующего термина будет, так что вам нужно его количественно оценить.
data Ex f = forall a. Ex (f a)
Проблема заключается в следующем: как вы восстанавливаете информацию о типе, чтобы иметь возможность манипулировать условиями? Мы можем связать Comb
с другим значением, которое вы можете сопоставлять шаблону во время выполнения до . Изучите тип Comb
. Вот комбинатор для спаривания вещей.
data (f :*: g) i = f i :*: g i
(я поднял оба этих типов из the Hasochism paper.) :*:
пара до двух типов, обеспечивающих, что их индексы равны.Мы будем использовать его вместе с Ex
для имитации зависимой пары или sigma Тип: пара значений, для которых тип второго компонента зависит от значения первого. Идея состоит в том, что f
будет GADT, который сообщает вам что-то о своем индексе, поэтому сопоставление шаблонов на f
дает вам информацию о типе g
.
type Sg f g = Ex (f :*: g)
pattern Sg x y = Ex (x :*: y)
Теперь умная часть: придумал GADT, в котором рассказывается о типе комбинаторного термина.
data Typey t where
Unity :: Typey Unit
Arry :: Typey a -> Typey b -> Typey (a :-> b)
Typey
называется одноточечно. Для данного t
существует ровно одно значение типа Typey t
. Поэтому, если у вас есть значение Typey t
, вы знаете все, что нужно знать о t
.
Синглтонские значения в конечном счете являются взломом. Typey
не Type
; это резервная копия уровня данных для дублированной копии типа Type
. В реальной системе с типичным типом вам не нужен одноэлементный клей для прикрепления материала уровня ценности к материалам типа, потому что различия там нет.
Наши экзистенциально-количественные комбинаторы теперь выглядят так. AComb
упаковывает Comb
с представлением времени выполнения. Этот метод позволяет гарантировать, что в коробке Comb
хорошо напечатан; мы просто не можем сказать статически, что это за тип.
type AComb = Sg Typey Comb
Как мы пишем ($$)
, который пытается применить AComb
к другому AComb
? Нам нужно сопоставить шаблон по связанным с ними Typey
с, чтобы узнать, можно ли применять одно к другому. В частности, нам понадобится способ узнать, равны ли два типа.
Приходит Пропозициональное равенство, доказательство GADT, что две вещи уровня уровня равны. Вы можете указать значение Refl
, если вы можете объяснить GHC, что a
и b
на самом деле то же самое. И наоборот, если вы сопоставляете шаблон по Refl
, тогда GHC добавит a ~ b
в контекст ввода.
data a :~: b where
Refl :: a :~: a
withEq :: a :~: b -> (a ~ b => r) -> r
withEq Refl x = x
Вот функция помощника, чтобы поднять пару равенств через :->
конструктора.
arrEq :: (a :~: c) -> (b :~: d) -> (a :-> b) :~: (c :-> d)
arrEq Refl Refl = Refl
Как и было обещано, мы можем записать функцию, чтобы проверить, являются ли два Type
s равны. Мы продолжаем с помощью сопоставления шаблонов на их ассоциированном одноэлементном Typey
с, если не найти типы, которые несовместимы. Если тест равенства преуспевает, трофеи являются доказательством того, что типы равны.
tyEq :: Typey t -> Typey u -> Maybe (t :~: u)
tyEq Unity Unity = Just Refl
tyEq (Arry a b) (Arry c d) = liftA2 arrEq (tyEq a c) (tyEq b d)
tyEq _ _ = Nothing
withTyEq :: Typey t -> Typey u -> (t ~ u => a) -> Maybe a
withTyEq t u x = fmap (\p -> withEq p x) (tyEq t u)
И наконец, мы можем написать $$
.Правило типизации выглядит следующим образом:
f : a -> b y : a
------------------- App
f y : b
То есть, если левая часть из $$
является типом функции и типа термина правого соответствует домену функции, мы можем ввести приложение. Поэтому для реализации этого правила необходимо проверить (используя withTyEq
) соответствие соответствующих типов, чтобы вернуть итоговый термин.
($$) :: AComb -> AComb -> Maybe AComb
Sg (Arry a b) x $$ Sg t y = withTyEq a t $ Sg b (x :$ y)
_ $$ _ = Nothing
Генерация Typey
терминов соответствует акту проверки типа. Другими словами, функция parse :: String -> AComb
должна выполнять как синтаксический анализ , так и. В реальных компиляторах эти две фазы разделены.
Таким образом, я бы посоветовал разобрать вход пользователя в текстовое дерево , которое допускает неработающие термины, а затем генерирует информацию для ввода отдельно.
data Expr = S | K | Expr :$ Expr
parse :: String -> Parser Expr
typeCheck :: Expr -> Maybe AComb
Весело упражнения (в более зависимым от типизированных языков) является изменение typeCheck
возвращать более подробное объяснение того, почему тип проверки не удалось, как этот бит псевдо-Agda:
data Void : Set where
Not : Set -> Set
Not a = a -> Void
data TypeError : Expr -> Set where
notArr : Not (IsFunction f) -> TypeError (f :$ x)
mismatch : Not (domain f :~: type x) -> TypeError (f :$ x)
inFunc : TypeError f -> TypeError (f :$ x)
inArg : TypeError x -> TypeError (f :$ x)
typeCheck : (e : Expr) -> Either (TypeError e) AComb
Вы также можете сделать typeCheck
более точным, убедившись, что он не изменит срок, который вы ему даете (другое упражнение).
Для дальнейшего ознакомления см. The View from the Left, в котором имеется проверенная проверка типа для лямбда-исчисления.
Проблема с вашей кодировкой: 'Typey' является мономорфным. То есть вы не можете использовать 'help :: AComb' для себя. Для этого вам нужно будет добавить явные переменные типа и изменить 'tyEq' на' tyUnify'. – user3237465
Да, это недостаток просто типизированных систем в целом. Я замалчивал его в ответе, потому что он делает код значительно сложнее, до такой степени, что (я думаю), затраты перевешивают преимущества терминов, индексированных по типу. –
Это именно то объяснение, которое я ищу. Позор, это сложнее с полиморфными типами, но это очень помогает. –