在Haskell中,如何根据GADT将无类型的AST解析为类型化的AST?

时间:2016-05-03 00:41:22

标签: haskell pattern-matching typechecking applicative gadt

我在Haskell中编写了一个特定于域的语言,并且已经确定了 一个带有两个AST的设计:一个初始的无类型表示语法和 代表一切的最终类型。我把最后一个写成了 GADT可以更好地进行类型检查。

我认为几乎正在工作,但我在编写一个函数时遇到了麻烦 转换初始 - > final(检查类型,以及其他一些未显示的内容, 像所有引用都对应一个变量)。

这是一个简单的例子:

{-# 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都应该是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,但我没有理解这个解释。它似乎 我不想要模式匹配。

更新:我必须在没有注意的情况下弄乱别的东西。模式匹配工作,如Nikita所示。

所以我摆弄着试图强行进入的东西 正确的形状,但尚未获得任何实质性的东西。如果是这样的话 只是Either String SomeValue我想使用应用程序,对吧?是吗 可以为他们添加另一级别的展开+类型检查吗?我怀疑this answer接近我想要的,因为问题非常相似,但我再也不理解。 This也可能是相关的。

更新:That first answer正是我想要的。但我无法看到chi如何在没有额外Type类型的情况下编写下面的中间版本。这是一个有效的解决方案。诀窍是使用仅代表TypedExpr return 类型(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中的两个TypedExpr2必须匹配, 因此,如果您尝试使用错误的整体返回类型,它会抓住您 结束。精彩!

3 个答案:

答案 0 :(得分:2)

我的建议是使用&#34; plain&#34;你的类型世界的表示(带有解释类型函数),然后在Sing中存储你的存在类型的TypedExpr leton,即类似

{-# LANGUAGE DataKinds, KindSignatures, TypeFamilies #-}

data Type = TInt | TString

type family InterpT (a :: Type) where
  InterpT TInt = Int
  InterpT TString = String

-- plus the usual singletons stuff
-- ...

-- and finally
data Typed (a :: Type) where ...

data TypedExpr where
  TypedExpr :: Sing a -> Typed a -> TypedExpr

这样,您可以执行类似

的操作
check (UAdd e1 e2) = do
  TypedExpr t1 e1' <- check e1
  TypedExpr t2 e2' <- check e2
  case testEquality t1 t2 of
    Just Refl -> ... use e1' and e2' here, you know they have the same Type
    Nothing -> Left ...

找到一个完全充实的示例here

答案 1 :(得分:0)

使用以下解决方案可以轻松回答您的确切问题:

m_p

然而,那里有很多关于设计迷宫的迹象。

您是否意识到,一旦您将某些内容放入check (UAdd (UNum a) (UNum b)) = Right $ TypedExpr $ TAdd (TNum a) (TNum b) ,您就会丢失有关TypedExpr类型的所有信息?这会使您的a函数毫无意义。

我知道您这样做是因为它是您统一GADT类型的唯一方式,否则您无法实现check功能。但实际上,它只是证明您对错误进行了建模并且GADT可能不适合您的用例。

我不明白为什么check构造函数超过UAdd值,而不是Untyped,我不明白是什么让你选择这个多-stage AST策略。

我可以继续,但我会在这里打破,只是建议你重新设计你的模型。

答案 2 :(得分:-1)

从技术上讲,它可行,但它非常不方便:你必须“挖掘”直到找到GADT构造函数。

check :: Untyped -> Either String TypedExpr
check (UNum n) = return $ TypedExpr $ TNum n
check (UStr s) = return $ TypedExpr $ TStr s
check (UAdd t1 t2) = do
    t1 <- check t1
    t2 <- check t2
    case (t1, t2) of
      (TypedExpr (TNum x)     , TypedExpr (TNum y))
         -> return $ TypedExpr $ TAdd (TNum x    ) (TNum y)
      (TypedExpr (TAdd x1 x2) , TypedExpr (TNum y))
         -> return $ TypedExpr $ TAdd (TAdd x1 x2) (TNum y)
      (TypedExpr (TNum x)     , TypedExpr (TAdd y1 y2))
         -> return $ TypedExpr $ TAdd (TNum x    ) (TAdd y1 y2)
      (TypedExpr (TAdd x1 x2) , TypedExpr (TAdd y1 y2))
         -> return $ TypedExpr $ TAdd (TAdd x1 x2) (TAdd y1 y2)
      _ -> Left "type error"

我会寻找其他选择。当构造函数的数量很大时,上述方法会遭受组合爆炸。