You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 08:01:48