Haskell AST Annotation with Fix

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Haskell AST Annotation with Fix相关的知识,希望对你有一定的参考价值。

我正在努力在Haskell中创建一个AST。我想添加不同的注释,例如类型和位置信息,所以我最终使用fixplate。但是,我在网上找不到任何例子,我遇到了一些困难。

我按照fixplate的推荐设置了我的AST(有些条纹):

data ProgramF a
  = Unary a
          Operator
  | Number Int
  | Let { bindings :: [(Identifier, a)]
        , body :: a }

type Program = Mu ProgramF

接下来添加标签我创建了另一种类型,以及一个基于树遍历添加标签的函数。

type LabelProgram = Attr ProgramF PLabel

labelProgram :: Program -> LabelProgram
labelProgram =
  annMap (PLabel . show . fst) . (snd . synthAccumL (i x -> (i + 1, (i, x))) 0)

但是,除此之外,我遇到了一些问题。例如,我正在尝试编写一个对AST进行一些转换的函数。因为它需要一个标签来运行,我已经制作了类型LabelProgram -> Program,但我认为我在这里做错了。下面是一部分函数的片段(一个更简单的部分):

toANF :: LabelProgram -> Program
toANF (Fix (Ann label (Let {bindings, body}))) = Fix $ Let bindingANF nbody
  where
    bindingANF = map ((i, e) -> (i, toANF e)) bindings
    nbody = toANF body

我觉得我在这里工作在错误的抽象层面。我应该明确地匹配Fix Ann ...并像这样返回Fix ...,还是我使用fixplate错了?

另外,我担心如何概括功能。我怎样才能使我的函数一般用于Programs,LabelPrograms和TypePrograms?

答案

编辑:使用通用注释添加ProgramFs函数的示例。

是的,至少在toANF的情况下,你错了。

toANF,请注意你的Let bindingANF nbodybindingANFnbody的伴随定义只是fmap toANF对特定构造函数Let的重新实现。

也就是说,如果你为你的Functor派生了一个ProgramF实例,那么你可以重新编写你的toANF片段:

toANF :: LabelProgram -> Program
toANF (Fix (Ann label l@(Let _ _))) = Fix (fmap toANF l)

如果toANF只是剥离标签,那么这个定义适用于所有构造函数而不仅仅是Let,因此您可以删除模式:

toANF :: LabelProgram -> Program
toANF (Fix (Ann label l)) = Fix (fmap toANF l)

现在,根据@Regis_Kuckaertz的评论,你刚刚重新实现了forget,定义如下:

forget = Fix . fmap forget . unAnn . unFix

关于编写在ProgramLabelProgram等上一般工作的函数,我认为在(单个)注释中编写泛型函数更有意义:

foo :: Attr ProgramF a -> Attr ProgramF a

并且,如果您确实需要将它们应用于未注释的程序,请定义:

type ProgramU = Attr ProgramF ()

ProgramU中的“U”代表“单位”。显然,如果确实需要,您可以轻松编写翻译器以与Programs一起使用ProgramUs:

toU :: Functor f => Mu f -> Attr f ()
toU = synthetise (const ())

fromU :: Functor f => Attr f () -> Mu f
fromU = forget

mapU :: (Functor f) => (Attr f () -> Attr f ()) -> Mu f -> Mu f
mapU f = fromU . f . toU

foo' :: Mu ProgramF -> Mu ProgramF
foo' = mapU foo

作为一个具体的 - 如果是愚蠢的 - 例子,这里有一个函数将Lets与多个绑定分离成嵌套的Lets与单例绑定(因此打破了Program语言中的相互递归绑定)。它假定多绑定Let上的注释将被复制到每个生成的单例Lets:

splitBindings :: Attr ProgramF a -> Attr ProgramF a
splitBindings (Fix (Ann a (Let (x:y:xs) e)))
  = Fix (Ann a (Let [x] (splitBindings (Fix (Ann a (Let (y:xs) e))))))
splitBindings (Fix e) = Fix (fmap splitBindings e)

它可以应用于示例Program

testprog :: Program
testprog = Fix $ Unary (Fix $ Let [(Identifier "x", Fix $ Number 1), 
                                   (Identifier "y", Fix $ Number 2)] 
                                  (Fix $ Unary (Fix $ Number 3) NegOp))
                       NegOp

像这样:

> mapU splitBindings testprog
Fix (Unary (Fix (Let {bindings = [(Identifier "x",Fix (Number 1))],
body = Fix (Let {bindings = [(Identifier "y",Fix (Number 2))], 
body = Fix (Unary (Fix (Number 3)) NegOp)})})) NegOp)
>

这是我完整的工作示例:

{-# LANGUAGE DeriveFunctor #-}
{-# OPTIONS_GHC -Wall #-}

import Data.Generics.Fixplate

data Identifier = Identifier String deriving (Show)
data PLabel = PLabel deriving (Show)
data Operator = NegOp deriving (Show)

data ProgramF a
  = Unary a
          Operator
  | Number Int
  | Let { bindings :: [(Identifier, a)]
        , body :: a }
  deriving (Show, Functor)
instance ShowF ProgramF where showsPrecF = showsPrec

type Program = Mu ProgramF
type LabelProgram = Attr ProgramF PLabel

splitBindings :: Attr ProgramF a -> Attr ProgramF a
splitBindings (Fix (Ann a (Let (x:y:xs) e)))
  = Fix (Ann a (Let [x] (splitBindings (Fix (Ann a (Let (y:xs) e))))))
splitBindings (Fix e) = Fix (fmap splitBindings e)

toU :: Functor f => Mu f -> Attr f ()
toU = synthetise (const ())

fromU :: Functor f => Attr f () -> Mu f
fromU = forget

mapU :: (Functor f) => (Attr f () -> Attr f ()) -> Mu f -> Mu f
mapU f = fromU . f . toU

testprog :: Program
testprog = Fix $ Unary (Fix $ Let [(Identifier "x", Fix $ Number 1), 
                                   (Identifier "y", Fix $ Number 2)] 
                                  (Fix $ Unary (Fix $ Number 3) NegOp))
                       NegOp

main :: IO ()
main = print $ mapU splitBindings testprog

以上是关于Haskell AST Annotation with Fix的主要内容,如果未能解决你的问题,请参考以下文章

在 Haskell 中不变异树

德克萨斯州Haskell郡300英亩售价2万5千美元

在具有 O(1) 元素访问的 Haskell 中实现高效的拉链式数据结构

在 Haskell 中实现一个高效的滑动窗口算法

肝功能AST/ALT是啥

Groovy编译时元编程 ( 利用注解进行 AST 语法树转换 | 定义注解并使用 GroovyASTTransformationClass 注明 AST 转换接口 | AST 转换接口实现 )