haskell ghc generic-programming template-haskell scrap-your-boilerplate

¿Anotación gratuita de AST en Haskell?



ghc generic-programming (4)

Esta pregunta es muy similar a una anterior que habla sobre la anotación particular de la ubicación de origen. La solución que encuentro más elegante es volver a definir Expr y Def para proporcionar un constructor que contenga una anotación:

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

He estado jugando con el compilador Elm, que está escrito en Haskell.

Me gustaría comenzar a implementar algunas optimizaciones para ello, y parte de esto implica atravesar el AST y agregar "anotaciones" a ciertos nodos, como llamadas de cola, etc.

Sé que puedo usar SYB o Uniplate para hacer el recorrido, pero me pregunto si hay una forma gratuita de lidiar con los tipos.

Entonces, supongamos que tenemos un montón de tipos algebraicos para nuestro AST:

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

Si estuviera escribiendo la placa de repetición, haría nuevos tipos como este:

data AnnotatedExpr = PlusExpr Expr Expr [Annotation] ... data AnnotatedDef = TypeAlias String [String] Type [Annotation] ...

Esta es una gran cantidad de cosas que escribir, y parece una buena práctica evitar esto.

Podría escribir algo como esto:

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

Entonces solo tendría un árbol de anotación paralelo al AST. Pero no hay garantía de que estos árboles tengan la misma estructura, por lo que perdemos la seguridad de tipo.

Así que me pregunto, ¿existe una solución elegante / recomendada para evitar la repetición, pero aún así anotar un árbol de una manera segura? ¿Para reemplazar cada nodo con uno equivalente, más una lista de anotaciones que se usarán en la compilación más adelante?


Si deja abierta la recursión en su tipo de datos, terminará sufriendo un constructor adicional en todas partes, pero podrá colocar anotaciones libremente sin cambiar la mayor parte de su árbol esquelético.

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

Este estilo es mucho más fácil cuando puedes escribir la mayoría de tus cálculos como Hutton -algebras, ya que se compondrán mejor.

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)

Lo que también es bueno es que si no te importa dejar cierta cantidad de enlaces en vivo en el nivel de Haskell (como en una eDSL de profundidad media), entonces la mónada Free Hutton es ideal para crear AST y la Cofree Hutton es esencialmente lo que se ha Annotated es.

Aquí hay una manera de construir anotaciones de abajo hacia arriba.

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

de tal manera que con las instancias de Show y Num apropiadas

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

Y así es como puedes despojarlos.

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

Vea here una descripción de cómo podría lograr este tipo de trabajo AST con TDA recursivas recíprocas, aseguradas por el comentario de Gallais a continuación.


También es posible escribir una plantilla de macros de Haskell que convierte un tipo de datos en uno anotado.


También puede utilizar las gramáticas de atributo para las anotaciones. Si necesita muchas anotaciones diferentes, el enfoque de las gramáticas se escalará mejor. Hay pocas bibliotecas y preprocesadores AG en Hackage, una es uuagc que se usa para compilar el compilador Haskell UHC / EHC.