Haskell中不含Boilerplate的AST注释?

时间:2014-11-26 19:49:46

标签: haskell ghc generic-programming template-haskell scrap-your-boilerplate

我一直在使用用Haskell编写的Elm编译器。

我想开始为它实现一些优化,其中一部分涉及遍历AST并向某些节点添加“注释”,例如尾调用等。

我知道我可以使用SYB或uniplate进行遍历,但我想知道是否有一种无样板的方法来处理类型。

所以,假设我们的AST有一堆代数类型:

data Expr = PlusExpr Expr Expr ...

data Def = TypeAlias String [String] Type ...

如果我正在编写样板文件,我会创建这样的新类型:

data AnnotatedExpr = PlusExpr Expr Expr [Annotation] ...

data AnnotatedDef = TypeAlias String [String] Type [Annotation] ...

这是很多写的boilderplate,似乎是避免这种做法的好习惯。

我可以这样写:

Data AnnotationTree =  Leaf  [Annotation]
     | Internal [AnnotationTree] [Annotation]

然后我只有一个与AST并行运行的注释树。但是不能保证这些树具有相同的结构,因此我们失去了类型安全性。

所以我想知道,是否有一个优雅/推荐的解决方案,以避免样板,但仍然以类型安全的方式注释树?要用等效的节点替换每个节点,还有稍后将在编译中使用的注释列表?

4 个答案:

答案 0 :(得分:25)

如果你将数据类型中的递归保持打开状态,你最终会在任何地方受到额外的构造函数的影响,但可以在不改变大部分骨架树的情况下自由分层注释。

data Hutton x    -- non-recursive functor type
  = Int Int | Plus x x
  deriving Functor

newtype Tie f = Tie (f (Tie f))

data Annotate f a = Annotate { annotation :: a, layer :: (f (Annotate f a)) }

type Unannotated = Tie      Hutton
type Annotated a = Annotate Hutton a

当您将大部分计算都编写为Hutton - 代数时,这种风格会更容易,因为它们会更好地构成。

interp :: Hutton Int -> Int
interp (Int i)    = i
interp (Plus a b) = a + b

runUnannotated :: Functor f => (f x -> x) -> Tie f -> x
runUnannotated phi (Tie f) = phi (fmap (runUnannotated phi) f)    

runAnnotated :: Functor f => (f x -> x) -> Annotate f a -> x
runAnnotated phi (Annotate _ f) = phi (fmap (runAnnotated phi) f)

如果您不介意在Haskell级别中使用一些绑定(例如在中等深度的eDSL中),那么Free Hutton monad非常适合构建AST和{ {1}} comonad本质上就是Cofree Hutton

这是一种从下往上构建注释的方法。

Annotated

使用适当的annotate :: Functor f => (f b -> b) -> Tie f -> Annotate f b annotate phi = runUnannotated $ \x -> Annotate (phi (fmap annotation x)) x memoize :: Unannotated -> Annotated Int memoize = annotate interp Show个实例

Num

以下是剥离它们的方法

λ> memoize (2 + (2 + 2))
Annotate 6 (Plus (Annotate 2 (Int 2)) (Annotate 4 (Plus (Annotate 2 (Int 2)) (Annotate 2 (Int 2)))))

请参阅here,了解如何使用相互递归的ADT实现这种AST工作,由Gallais在下面的评论保证。

答案 1 :(得分:6)

这个问题非常类似于past one谈论源位置的特定注释。我发现最优雅的解决方案是重新定义ExprDef以提供包含注释的构造函数:

data Expr = PlusExpr Expr Expr
          | AnnotatedExpr Annotation Expr
          ...

答案 2 :(得分:4)

您还可以使用属性语法进行注释。如果您需要许多不同的注释,语法方法将更好地扩展。 Hackage上有很少的AG库和预处理器,一个是uuagc,用于构建UHC / EHC Haskell编译器。

答案 3 :(得分:2)

也可以编写一个Template Haskell宏,将数据类型转换为带注释的数据类型。