使用GADT和构造子集的C语言AST

时间:2019-06-17 17:30:00

标签: haskell

我想定义类型级别的安全C语言AST。到目前为止,我想到了这样的东西:

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ImpredicativeTypes #-}
{-# LANGUAGE RankNTypes #-}

data Defn
  = Func String [Block]

type Block = forall a. BlockItem a
type Stmt = BlockItem StmtType

data BlockItemKind = StmtType

data BlockItem :: BlockItemKind -> * where
  Var :: String -> Expr -> BlockItem a
  SideEff :: Expr -> BlockItem StmtType
  Return :: Expr -> BlockItem StmtType

data Expr
  = Lit Int

关键点是BlockItem数据类型。在C标准中,有两个非常相似的结构–块和语句。块基本上只是语句列表,也可以包含变量声明。在这段代码中,我试图将语句声明为块项目构造函数的子集(如SideEffReturn)。

但是,此代码无法正常工作。考虑下面的代码正在打印此AST:

showDefn :: Defn -> String
showDefn (Func name block) = "func " ++ name ++ " . " ++ show (showBlock <$> block)

showBlock :: Block -> String
showBlock (Var name e) = name ++ " = " ++ showExpr e ++ ";"
showBlock e = showStmt e

showStmt :: Stmt -> String
showStmt (SideEff e) = showExpr e ++ ";"
showStmt (Return e) = "return " ++ showExpr e ++ ";"

showExpr :: Expr -> String
showExpr (Lit x) = show x

当我用showDefn评估[Block]时,一切正常。但是,当我使用Stmt类型时,此代码不起作用:

main :: IO ()
main = do
  -- FOLLOWING WORK
  print $ showDefn (Func "foo" [Var "a" (Lit 2), Var "b" (Lit 3)])

  -- FOLLOWING DOES NOT WORK
  print $ showDefn (Func "foo" [Return (Lit 0)])
  print $ showDefn (Func "foo" [Var "a" (Lit 1), SideEff (Lit 2)])

您可以运行代码here

问题出在哪里?我什至不确定这是否是正确的设计。

1 个答案:

答案 0 :(得分:2)

(Haskell专家提示:如果您发现必须使用ImpredicativeTypes扩展名,请将手从键盘上移开,然后再离开计算机。)

无论如何,要详细说明@luqui的注释,类型Defn等效于:

data Defn = Func String [forall a. BlockItem a]

这是String和列表的产品类型。列表中的元素具有类型forall a. BlockItem a,对于任何BlockItem a(由值的调用者/用户选择),它可以是a的事物类型。正如@luqui所指出的,Var "a" (Lit 2)具有这种类型-它对于任何可能的BlockItem a都可以是a,但是您的其他阻止项则不能。例如,当Return (Lit 0)BlockItem a时,a只能是StmtType,因此不能放在[forall a. BlockItem a]列表中-类型是太笼统了。

它类似于以下类型,它允许存储可以是任何Num类型的内容:

data NumList = NL [forall a. Num a => a]

由于318 - 1可以是任何类型的Num,我们可以将它们放在列表中:

mylist = NL [3, 18-1]

稍后,我们可以提取此列表的元素之一:

NL [_,x] = mylist

并将其视为我们想要的任何类型的Num

> x :: Integer
17
> x :: Double
17.0

但是我们不能将特定的Num类型(例如Int)放入列表:

badlist = NL [length "hello"]  -- type error

如果可以的话,我们可以这样写:

> let NL [x] = badlist in sqrt x

使用sqrt中的Int,所有“类型地狱”都会崩溃。

所以,这就是您在做的错误。很难告诉您应该怎么做正确。为什么没有GADT和DataKinds,以下内容对您不起作用?

data Defn = Func String [BlockItem]

data BlockItem
  = Var String Expr
  | Stmt Statement

data Statement
  = SideEff Expr
  | Return Expr

data Expr
  = Lit Int