使用fixplate为Haskell AST添加注解遇阻,求技术指导
我之前用fixplate处理过类似的AST注解需求,给你捋捋具体的实现步骤和思路吧,应该能帮你推进下去!
首先,先明确fixplate的核心逻辑:它通过**递归函子(Recursive Functor)**和Mu(最小不动点)来定义AST,而添加注解的核心是用函子转换器(比如Ann)把原AST节点和注解绑定在一起,或者用Attr来动态计算节点属性。
先补全你现有代码里的依赖类型(假设你还没定义的话),并确保ProgramF实现Functor——这是fixplate所有操作的基础:
import Data.Fixplate import Data.Fixplate.Lens -- 用于便捷访问注解的透镜 -- 先定义你需要的基础类型 data Operator = Neg | Not deriving (Show, Eq) type Identifier = String data Type = IntType | BoolType deriving (Show, Eq) -- 自定义位置信息类型,也可以用haskell-src-exts等库的现成类型 data SrcSpan = SrcSpan { startLine :: Int, startCol :: Int, endLine :: Int, endCol :: Int } deriving (Show, Eq) -- 你的原始AST函子,必须导出Functor(自动推导即可) data ProgramF a = Unary a Operator | Number Int | Let { bindings :: [(Identifier, a)], body :: a } deriving (Show, Eq, Functor) -- 原始无注解AST类型 type Program = Mu ProgramF
第一步:定义注解类型
把你需要的位置、类型信息打包成一个注解结构体:
data NodeAnnotation = NodeAnnotation { annSrcSpan :: SrcSpan -- 节点的源代码位置 , annType :: Maybe Type -- 节点的类型(推导前用Nothing,推导后填充) } deriving (Show, Eq)
第二步:创建带注解的AST类型
用fixplate提供的Ann函子转换器,把原ProgramF包装成带注解的函子,再用Mu生成递归类型:
-- 带注解的AST类型:Mu (Ann 注解类型 原函子类型) type AnnotatedProgram = Mu (Ann NodeAnnotation ProgramF)
第三步:构建带注解的AST节点
写个辅助函数简化注解的添加,避免手动嵌套Fix和Ann:
-- 给AST节点附加注解的辅助函数 annotate :: NodeAnnotation -> ProgramF AnnotatedProgram -> AnnotatedProgram annotate ann node = Fix (Ann ann node) -- 示例:创建一个带位置的Number节点 mkNumber :: SrcSpan -> Int -> AnnotatedProgram mkNumber span n = annotate (NodeAnnotation span Nothing) (Number n) -- 示例:创建一个带位置的Unary节点,引用子节点 mkUnary :: SrcSpan -> Operator -> AnnotatedProgram -> AnnotatedProgram mkUnary span op child = annotate (NodeAnnotation span Nothing) (Unary child op) -- 示例:创建Let节点 mkLet :: SrcSpan -> [(Identifier, AnnotatedProgram)] -> AnnotatedProgram -> AnnotatedProgram mkLet span binds body = annotate (NodeAnnotation span Nothing) (Let binds body)
第四步:遍历并修改注解(比如类型推导)
用fixplate的transform函数可以递归遍历整个AST,修改每个节点的注解。比如实现一个简单的类型推导,填充annType:
-- 类型推导:遍历带注解的AST,计算每个节点的类型 inferTypes :: AnnotatedProgram -> AnnotatedProgram inferTypes = transform $ \(Ann ann node) -> let newType = case node of -- 数字节点类型是IntType Number _ -> Just IntType -- 负号操作符的类型和子节点一致 Unary child Neg -> annType (unFix child ^. annAnn) -- 逻辑非操作符的类型和子节点一致(假设子节点是BoolType) Unary child Not -> annType (unFix child ^. annAnn) -- Let表达式的类型等于body的类型 Let _ body -> annType (unFix body ^. annAnn) in Ann (ann { annType = newType }) node
这里的unFix用来解开Mu的Fix构造器,^. annAnn是fixplate提供的透镜,用来快速获取Ann里的注解部分。
另一种方式:用Attr动态计算属性
如果不需要把注解永久存在AST里,只是遍历的时候临时计算(比如只需要在某个阶段获取节点类型),可以用Attr:
-- 定义一个计算节点类型的Attr typeAttr :: Attr ProgramF Type typeAttr = attr $ \case Number _ -> IntType Unary ty Neg -> ty Unary ty Not -> ty Let _ ty -> ty -- 使用示例:获取某个无注解AST节点的类型 exampleProgram :: Program exampleProgram = Fix (Unary (Fix (Number 5)) Neg) exampleNodeType :: Type exampleNodeType = getAttr typeAttr exampleProgram
关键注意点
- 必须确保你的
ProgramF是Functor实例,如果自动推导失败(比如复杂的记录结构),可以手动实现fmap。 - fixplate的Haddock文档里有很多核心函数的细节,比如
cata(折叠AST)、ana(生成AST)、para(带自身上下文的折叠),可以多看看这些函数的用法。
内容的提问来源于stack exchange,提问作者John Howard
相关产品推荐
相关产品推荐

