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

Haskell 使用GADT和构造函数子集的C语言AST,haskell,Haskell,我想定义类型级别安全的C语言AST。到目前为止,我想出了这样的办法: {-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE KindSignatures #-} {-# LANGUAGE ImpredicativeTypes #-} {-# LANGUAGE RankNTypes #-} data Defn = Func String [Block] type Block = forall a. BlockItem

我想定义类型级别安全的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
关键点是
块项
数据类型。在C标准中,有两种非常相似的结构——块和语句。块基本上只是语句列表,也可以包含变量声明。在这段代码中,我试图将语句声明为块项构造函数的子集(如
SideEff
Return

但是,此代码不能按预期工作。考虑下面的代码打印这个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
您可以运行代码

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

(Haskell pro提示:如果你发现自己不得不使用
ImpredicativeTypes
扩展名,请把手从键盘上拿开,远离电脑。)

无论如何,要详细说明@luqui的评论,类型
Defn
相当于:

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

这是一个
字符串和一个列表的产品类型。列表中的元素对于所有a都具有类型
。BlockItem a
,这是任何
a
(由值的调用方/用户选择)都可以成为
BlockItem a
的类型。正如@luqui指出的,
Var“a”(Lit 2)
有这种类型——它可以是任何可能的
a
块项a
,但您的其他块项不能。例如,当
a
StmtType
时,
Return(Lit 0)
只能是
BlockItem a
,因此它不能放在
[对于所有a.BlockItem a]
列表中——该类型对它来说太一般了

它类似于以下类型,允许存储任何类型的
Num

data NumList = NL [forall a. Num a => a]
因为
3
18-1
可以是任何类型的
Num
,我们可以将它们放在列表中:

mylist = NL [3, 18-1]
badlist = NL [length "hello"]  -- type error
稍后,我们可以提取此列表中的一个元素:

NL [_,x] = mylist
并将其视为我们想要的任何类型的
Num

> x :: Integer
17
> x :: Double
17.0
但我们不能将特定的
Num
类型(例如
Int
)放入列表:

mylist = NL [3, 18-1]
badlist = NL [length "hello"]  -- type error
如果我们可以,那么我们可以写:

> let NL [x] = badlist in sqrt x
使用
Int
sqrt
,所有“类型地狱”都将崩溃

所以,这就是你做错的地方。很难说你应该怎样做才对。如果没有GADT和数据类型,为什么下面的内容不适合您

data Defn = Func String [BlockItem]

data BlockItem
  = Var String Expr
  | Stmt Statement

data Statement
  = SideEff Expr
  | Return Expr

data Expr
  = Lit Int

我不明白你想干什么。在C语言中,块只是一种语句类型(“复合语句”)。
。BlockItem a
是可以同时用每个语句类型标记的东西(只有
Var
具有此属性)。