Haskell AST 注释修复
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 ...
,还是我使用的固定板有误?
另外,我关心的是如何泛化函数。我怎样才能让我的函数一般适用于 Program
s、LabelProgram
s 和 TypeProgram
s?
编辑: 为 ProgramF
添加带有通用注释的函数示例。
是的,至少在 toANF
的情况下,你用错了。
在 toANF
中,请注意您的 Let bindingANF nbody
以及 bindingANF
和 nbody
的伴随定义只是针对特定构造函数的 fmap toANF
的重新实现Let
.
也就是说,如果您为 ProgramF
派生了一个 Functor
实例,那么您可以将 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
关于编写在 Program
、LabelProgram
等上通用的函数,我认为在(单个)注释中编写通用函数更有意义:
foo :: Attr ProgramF a -> Attr ProgramF a
并且,如果您确实需要将它们应用于未注释的程序,请定义:
type ProgramU = Attr ProgramF ()
其中 ProgramU
中的 "U" 代表 "unit"。显然,如果确实需要,您可以轻松编写翻译器以使用 Program
s 作为 ProgramU
s:
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
作为一个具体的——如果是愚蠢的——例子,这里有一个函数将具有多个绑定的 Let
s 分离为具有单例绑定的嵌套 Let
s(因此打破了Program
语言)。它假定多绑定 Let
上的注释将被复制到每个生成的单例 Let
s:
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。我想添加不同的注释,例如类型和位置信息,所以我最终使用了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 ...
,还是我使用的固定板有误?
另外,我关心的是如何泛化函数。我怎样才能让我的函数一般适用于 Program
s、LabelProgram
s 和 TypeProgram
s?
编辑: 为 ProgramF
添加带有通用注释的函数示例。
是的,至少在 toANF
的情况下,你用错了。
在 toANF
中,请注意您的 Let bindingANF nbody
以及 bindingANF
和 nbody
的伴随定义只是针对特定构造函数的 fmap toANF
的重新实现Let
.
也就是说,如果您为 ProgramF
派生了一个 Functor
实例,那么您可以将 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
关于编写在 Program
、LabelProgram
等上通用的函数,我认为在(单个)注释中编写通用函数更有意义:
foo :: Attr ProgramF a -> Attr ProgramF a
并且,如果您确实需要将它们应用于未注释的程序,请定义:
type ProgramU = Attr ProgramF ()
其中 ProgramU
中的 "U" 代表 "unit"。显然,如果确实需要,您可以轻松编写翻译器以使用 Program
s 作为 ProgramU
s:
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
作为一个具体的——如果是愚蠢的——例子,这里有一个函数将具有多个绑定的 Let
s 分离为具有单例绑定的嵌套 Let
s(因此打破了Program
语言)。它假定多绑定 Let
上的注释将被复制到每个生成的单例 Let
s:
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