Boilerplate-free annotation of ASTs in Haskell?

独自空忆成欢 提交于 2019-11-29 22:19:03

If you leave the recursion in your data type open you end up suffering an extra constructor everywhere, but can layer in annotations freely without changing most of your skeletal tree.

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

This style is much easier when you can write most of your computations as Hutton-algebras since they will compose better.

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)

What's also nice is that if you don't mind letting some amount of binding live in the Haskell level (such as in a middling-deep eDSL) then the Free Hutton monad is great for building ASTs and the Cofree Hutton comonad is essentially what Annotated is.

Here's a way to build annotations from the bottom up.

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

such that with the appropriate Show and Num instances

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

And here's how you can strip them

strip :: Annotated a -> Unannotated
strip = runAnnotated Tie

See here for a description of how you might achieve this kind of AST work with mutually recursive ADTs, insured by Gallais' comment below.

Thomas M. DuBuisson

This question is very similar to a past one talking about the particular annotation of source location. The solution I find most elegant is to re-define Expr and Def to provide a constructor that contains an annotation:

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

You can also use attribute grammars for annotations. If you need many different annotations, the grammars approach will scale better. There are few AG libraries and preprocessors on Hackage, one is uuagc which is used to build UHC/EHC Haskell compiler.

It is also possible to write a Template Haskell macros which converts a datatype into an annotated one.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!