在Haskell中编写编译器时,我在使用嵌套数据类型时遇到了一些特定问题。我将定义类似
的ADTdata AST = AST [GlobalDecl]
data GlobalDecl = Func Type Identifier [Stmt] | ...
data Stmt = Assign Identifier Exp | ...
data Exp = Var Identifier | ...
在对AST执行某些转换时,我可能希望简单地使用在表达式中使用的变量来携带一些额外的数据。到目前为止,我所考虑的所有这些选项似乎都相当尴尬。我可以创建一个新的数据类型:
data Exp' = Var' Identifier ExtraInfo | ...
但这意味着我需要一个新的定义Stmt'
,GDecl'
,以便形成稍微改变的AST'
。另一种选择是将另一个数据构造函数添加到原始Exp
,但只在程序的一个特定部分中使用它:
data Exp = Var Identifier | Var' Identifier ExtraInfo | ...
如果你这样做,那么类型检查器就不能再阻止你在程序的其他部分错误地使用Var'
了。
第三种选择是简单地保留额外信息,即使它与程序的其余部分无关:
data Exp = Var Identifier ExtraInfo | ...
可行,但它很难看,特别是如果你只需要简要的额外信息。现在我只是将额外的信息放在Map Indentifier ExtraInfo
中,并使用AST
明确地或通过状态monad携带它。例如,如果您需要使用不同的信息注释相同Identifier
的不同出现,这可能会很快变得尴尬。
有没有人有任何优雅的技术来注释嵌套数据类型?
答案 0 :(得分:4)
使用额外数据标记结构的一个选项是使用更高的kinded类型参数。如果你只需要标记变量,你可以这样做。
data AST f = AST [GlobalDecl f]
data GlobalDecl f = Func Type Identifier [Stmt f] | ...
data Stmt = Assign Identifier (Exp f) | ...
data Exp f = Var (f Identifier) | ...
这与彼得建议的类似,但它不是使类型完全通用,而是仅参数化您想要改变的部分。
您将使用AST Identity
获取原始的未标记结构,或者您可以使用类似AST ((,) ExtraInfo)
的类型将Var (f Identifier)
转换为Var (ExtraInfo, Identifier)
。
如果您需要使用一些额外信息(例如令牌位置)标记AST的每个级别,您甚至可以将数据类型定义为
data AST f = AST [f (GlobalDecl f)]
data GlobalDecl f = Func (f (Type f)) (f (Identifier f)) [f (Stmt f)] | ...
data Stmt f = Assign (f (Identifier f)) (f (Exp f)) | ...
data Exp f = Var (f (Identifier f)) | ...
现在AST ((,) ExtraInfo)
将在语法树的每个分支点包含额外信息(授予,使用上述结构会有点麻烦)。
答案 1 :(得分:2)
如果你使所有类型更具多态性,如下所示:
data AST a = AST a
data GlobalDecl t i s = Func t i [s] | ...
data Stmt i e = Assign i e | ...
data Exp a = Var a | ...
然后你可以用元组暂时实例化它们 - 例如Exp (Int, Identifier)
- 用于中间计算。如有必要,您可以为上述具体类型制作newtype
s,以方便使用。