Я пишу доменный язык в Haskell и установил на дизайн с двумя АСТ: исходный нетипичный, который представляет синтаксис, и окончательный типизированный, который представляет все. Я пишу последний как GADT, чтобы получить лучшую проверку типов.В Haskell, как разобрать нетипизированный АСТ на типизированный, основанный на GADT?
Я думаю, что это почти работает, но у меня возникают проблемы писать функцию, которая преобразует начальное -> конечные (проверки типов, а также некоторые другие вещи, не показано, как все ссылки соответствуют переменной).
Вот упрощенный пример:
{-# LANGUAGE GADTs, StandaloneDeriving #-}
module Main where
-- untyped initial AST
data Untyped
= UNum Int
| UStr String
| UAdd Untyped Untyped
deriving (Show, Eq)
-- typed final AST
data Typed a where
TNum :: Int -> Typed Int
TStr :: String -> Typed String
TAdd :: Typed Int -> Typed Int -> Typed Int
deriving instance Eq (Typed a)
deriving instance Show (Typed a)
-- wrapper that allows working with a `Typed a` for any `a`
data TypedExpr where
TypedExpr :: Typed a -> TypedExpr
И это моя попытка функции check
. Базовые случаи просты:
check :: Untyped -> Either String TypedExpr
check (UNum n) = Right $ TypedExpr $ TNum n
check (UStr s) = Right $ TypedExpr $ TStr s
-- check (Uadd e1 e2) = ???
Но как я могу добавить Add
? Он может рекурсивно оценить вложенные выражения для значений типа Either String (TypedExpr (Typed a))
, но я не сумел разворачивать те, убедитесь, что типы выстраиваться (оба a
s должна быть Int
s), и повторно компресс позже. Я планировал сделать это все с матчей большой картины, но GHC не одобряет:
My brain just exploded
I can't handle pattern bindings for existential or GADT data constructors.
Instead, use a case-expression, or do-notation, to unpack the constructor.
Вот объяснил here, но я не понял объяснения. Кажется Я не хочу сопоставлять образцы.
Обновление: Я, должно быть, испортил что-то еще, не заметив. Работа с шаблонами, как показывает Никита.
Таким образом, я возился вокруг, пытаясь заставить вещи в правильной форме, но еще ничего не получил. Если бы это были только Either String SomeValue
Я бы хотел использовать аппликации, не так ли? Возможно ли добавить еще один уровень развертывания + тип проверки на них? Я подозреваю, что this answer близок к тому, что я хочу, так как вопрос очень похож, но опять я его не понимаю. This также могут быть связаны.
Обновление: That first answer - это то, что я хотел в конце концов. Но я не мог видеть, как до того, как Чи написал промежуточную версию ниже без дополнительного Type
. Вот рабочее решение. Хитрость в том, чтобы пометить TypedExpr
с с новым типом, представляющим только обратного типа (a
) в виде Typed a
:
data Returns a where
RNum :: Returns Int
RStr :: Returns String
-- extend TypedExpr to include the return type
data TypedExpr2 where
TypedExpr2 :: Returns a -> Typed a -> TypedExpr2
Таким образом check
не должен знать, является ли каждая Подвыражение является сырьевым TNum
или функция (как Add
), которая возвращает TNum
:
check :: Untyped -> Either String TypedExpr2
check (UNum n) = Right $ TypedExpr2 RNum (TNum n)
check (UStr s) = Right $ TypedExpr2 RStr (TStr s)
check (UAdd u1 u2) = do
-- typecheck subexpressions, then unwrap by pattern matching
TypedExpr2 r1 t1 <- check u1
TypedExpr2 r2 t2 <- check u2
-- check the tags to find out their return types
case (r1, r2) of
-- if correct, create an overall expression tagged with its return type
(RNum, RNum) -> return $ TypedExpr2 RNum $ TAdd t1 t2
_ -> Left "type error"
GHC достаточно умен, чтобы знать, что два a
s в любой TypedExpr2
должен совпадать, , чтобы он поймал вас, если вы попытаетесь использовать неправильный общий тип возврата на конце . Замечательно!
Как насчет чего-то вроде [этого] (http://stackoverflow.com/a/27838550/477476)? Дополнительную информацию см. В https://gergo.erdi.hu/blog/2015-02-05-typed_embedding_of_stlc_into_haskell/. – Cactus