首先,我有一个原始的AST定义,如下所示:
data Expr = LitI Int | LitB Bool | Add Expr Expr
我想对此进行概括,以便每个AST节点都可以包含一些额外的属性:
data Expr a = LitI Int a | LitB Bool a | Add (Expr a) (Expr a) a
通过这种方式,我们可以轻松地将属性附加到AST的每个节点中:
type ExprWithType = Expr TypeRep
type ExprWithSize = Expr Int
但是这种解决方案使访问属性字段变得困难,我们必须使用模式匹配并逐个处理它:
attribute :: Expr a -> a
attribute e = case e of
LitI _ a -> a
LitB _ a -> a
Add _ _ a -> a
如果可以通过原始AST的产品类型和指示属性的类型变量来定义AST,我们可以想象得到
type ExprWithType = (Expr, TypeRep)
type ExprWithSize = (Expr, Int)
然后我们可以简化属性访问函数,如下所示:
attribute = snd
但是我们知道,最外面的产品类型的属性不会递归地出现在子树中。
那么,有没有更好的解决方案?
通常来说,当我们要提取递归和类型的不同情况的公共字段时,我们遇到了这个问题。
答案 0 :(得分:2)
您可以“提升” Expr
的类型,例如:
data Expr e = LitI Int | LitB Bool | Add e e
现在我们可以定义一种数据类型,
data ExprAttr a = ExprAttr {
expression :: Expr (ExprAttr a),
attribute :: a
}
因此,这里的ExprAttr
有两个参数,expression
,这是一个Expr
会话,在树中有ExprAttr a
个,而attribute
这是a
。
因此,您可以处理ExprAttr
秒的AST。如果要使用“简单” AST,则可以定义以下类型:
ExprAttr
答案 1 :(得分:1)
您可能想看看Cofree
,其中f
将是您的递归数据类型,将递归的概念抽象为f-algebra,而a
将是递归数据类型。注释的类型。
Nate Faubion对这种方法和类似方法进行了非常深入的讨论,您可以在这里观看:https://www.youtube.com/watch?v=eKkxmVFcd74