将简单类型语言的无类型AST转换为GADT

时间:2015-06-01 02:46:53

标签: haskell gadt

我有一个ADT代表一个简单语言的AST:

data UTerm = UTrue
      | UFalse
      | UIf UTerm UTerm UTerm
      | UZero
      | USucc UTerm
      | UIsZero UTerm

此数据结构可以表示不遵循该类型的无效字词 语言规则,如UIsZero UFalse,所以我想使用GADT 强制执行良好的类型:

{-# LANGUAGE GADTs #-}

data TTerm a where
  TTrue :: TTerm Bool
  TFalse :: TTerm Bool
  TIf :: TTerm Bool -> TTerm a -> TTerm a -> TTerm a
  TZero :: TTerm Int
  TSucc :: TTerm Int -> TTerm Int
  TIsZero :: TTerm Int -> TTerm Bool

我的问题是键入检查UTerm并将其转换为TTerm。我的第一次 思想是UTerm -> Maybe (TTerm a),但这当然不起作用,因为 它对所有a无效。我甚至不知道这种类型是什么,因为 我们不知道a是Int还是Bool。然后我以为我可以写一个 a的每个可能值的不同类型检查函数:

import Control.Applicative

typecheckbool :: UTerm -> Maybe (TTerm Bool)
typecheckbool UTrue = Just TTrue
typecheckbool UFalse = Just TFalse
typecheckbool (UIsZero a) = TIsZero <$> typecheckint a
typecheckbool _ = Nothing

typecheckint :: UTerm -> Maybe (TTerm Int)
typecheckint UZero = Just TZero
typecheckint (USucc a) = TSucc <$> typecheckint a
typecheckint (UIf a b c) = TIf <$> typecheckbool a <*> typecheckint b <*> typecheckint c
typecheckint UTrue = Nothing
typecheckint UFalse = Nothing
typecheckint (UIsZero _) = Nothing

这适用于某些情况,适用于TIf要求的语言的子集 结果和替代是Ints(但实际上是TIf TTrue TFalse TTrue 完全有效),以及我们知道表达式的目标类型的地方 打字。

从UTerm转换为TTerm的正确方法是什么?

2 个答案:

答案 0 :(得分:6)

标准技术是定义存在类型:

data ETerm_ where
    ETerm_ :: TTerm a -> ETerm

在这种情况下,您可能还需要一些关于您拥有哪种类型的术语级证据; e.g。

data Type a where
    TInt :: Type Int
    TBool :: Type Bool

然后真正的ETerm看起来像这样:

data ETerm where
    ETerm :: Type a -> TTerm a -> ETerm

类型检查的有趣情况就像是

typeCheck (UIf ucond ut uf) = do
    ETerm TBool tcond <- typeCheck ucond
    ETerm tyt tt <- typeCheck ut
    ETerm tyf tf <- typeCheck uf
    case (tyt, tyf) of
        (TBool, TBool) -> return (ETerm TBool (TIf tcond tt tf))
        (TInt , TInt ) -> return (ETerm TInt  (TIf tcond tt tf))
        _ -> fail "branches have different types"

答案 1 :(得分:5)

作为@ DanielWagner答案的次要补充,你可能想要对类型相等性检查进行分解,例如

...
case (tyt, tyf) of
        (TBool, TBool) -> return (ETerm TBool (TIf tcond tt tf))
        (TInt , TInt ) -> return (ETerm TInt  (TIf tcond tt tf))
        _ -> fail "branches have different types"

这样做的一种方法是使用平等证人:

import Data.Type.Equality

typeEq :: Type a -> Type b -> Maybe (a :~: b)
typeEq TInt  TInt  = Just Refl
typeEq TBool TBool = Just Refl
typeEq _     _     = Nothing

typeCheck :: UTerm -> Maybe ETerm
typeCheck (UIf ucond ut uf) = do
    ETerm TBool tcond <- typeCheck ucond
    ETerm tyt tt <- typeCheck ut
    ETerm tyf tf <- typeCheck uf
    case typeEq tyt tyf of
        Just Refl -> return (ETerm tyt (TIf tcond tt tf))
        _         -> fail "branches have different types"

如果需要在类型检查例程的多个部分中检查类型相等,则此分解很方便。它还允许使用像(t1,t2)这样的对类型扩展语言,这需要结构递归方法来检查类型相等。

甚至可以为类型相等

编写完整的决策程序
{-# LANGUAGE EmptyCase #-}
typeEq2 :: Type a -> Type b -> Either (a :~: b) ((a :~:b) -> Void)
typeEq2 TInt  TInt  = Left Refl
typeEq2 TInt  TBool = Right (\eq -> case eq of)
typeEq2 TBool TBool = Left Refl
typeEq2 TBool TInt  = Right (\eq -> case eq of) 

但是,我想,除非您尝试对非常高级的类型(例如GADT)进行建模,否则可能不需要这样做。

上面的代码使用和空案例来检查eq可能具有的所有可能值。因为它有例如Int :~: Bool,并且没有与该类型匹配的构造函数,我们没有eq的可能值,因此不需要case分支。这将触发详尽的警告,因为确实没有未处理的情况(OT:我希望这些警告是实际错误)。

除了使用EmptyCase之外,您还可以使用类似case eq of _ -> undefined之类的内容,但在上述证明条款中使用底部是可疑的。