Ocaml 如何编写PPX重写器生成记录镜头?

Ocaml 如何编写PPX重写器生成记录镜头?,ocaml,metaprogramming,Ocaml,Metaprogramming,我正在写一个PPX重写器来简化定义。让我为普通读者回忆一下透镜是什么 关于镜头 与记录字段相关联的镜头是一对函数,允许提取记录并对其进行更新。以下是一个例子: module Lens = struct type ('a, 'b) t = { get : 'a -> 'b; set : 'b -> 'a -> 'a } end type car = { vendor: string; make: string; mileage: int; }

我正在写一个PPX重写器来简化定义。让我为普通读者回忆一下透镜是什么

关于镜头 与记录字段相关联的镜头是一对函数,允许提取记录并对其进行更新。以下是一个例子:

module Lens =
struct
  type ('a, 'b) t = {
    get : 'a -> 'b;
    set : 'b -> 'a -> 'a
  }
end

type car = {
  vendor: string;
  make: string;
  mileage: int;
}

let vendor_lens = {
  Lens.get = (fun x -> x.vendor);
  Lens.set = (fun v x -> { x with vendor = v })
}
vendor\u lens
允许我们获取
car
vendor
字段的值,并对其进行更新–这意味着返回
car
的新副本,该副本与原始副本的唯一区别在于
vendor
car的值。这听起来可能很平庸,但事实并非如此:因为镜头本质上是功能,它们可以组合,模块中充满了有用的功能。在复杂的代码库中,组合访问器的能力至关重要,因为它通过抽象从计算上下文到深度嵌套记录的路径来简化解耦。我最近还重构了我的眼镜,并采用了一个功能接口,这使得眼镜更加相关——至少对我来说是这样

生成透镜 上面的
vendor\u lens
的定义只不过是,没有理由不利用PPX重写器让我们简单地编写

type car = {
  vendor: string;
  make: string;
  mileage: int;
} [@@with_lenses]
并查看我们的汽车所需镜头的自动定义。ª

我决定解决这个问题,并可以制作:

  • 谓词
    是\u记录:Parsetree.structure\u item->bool
    识别类型记录定义

  • 函数
    label\u声明:Parsetree.structure\u item->string list
    可能会返回记录定义的记录声明列表–是的,我们可以使用一个选项将1和2粉碎在一起

  • 函数
    lens\u expr:string->Parsetree.structure\u item
    为给定字段声明生成镜头定义。不幸的是,在我编写这个函数之后,AlainFrisch发现了这个问题

  • 在我看来,这里有我想写的PPX重写器的基本部分。不过,我怎样才能把它们结合在一起呢


    在为镜头寻找PPX重写器时,我偶然发现了不少于五个博客或自述,涉及非常相同的
    汽车
    结构。在这里重复使用这个例子是一种卑鄙的尝试,试图让自己看起来像一个配备镜头的汽车司机俱乐部的全职成员。
    
    您的PPX项目的最终目标是构建一个类型为
    Ast\u mapper.mapper
    的映射器

    mapper
    是一种大型记录类型,它带有用于
    Parsetree
    数据类型的映射器函数,例如

    type mapper = { 
      ...
      structure : mapper -> structure -> structure;
      signature : mapper -> signature -> signature;
      ...
    }
    
    有一个默认映射器
    Ast\u映射器。默认映射器
    ,这是映射器的起点:您可以继承它并覆盖某些记录成员以供使用。对于镜头项目,您必须实现
    结构
    签名

    let extend super =
      let structure self str = ... in
      let signature self str = ... in
      { super with structure; signature }
    
    let mapper = extend default_mapper
    
    函数
    structure
    应扫描结构项,并为每个记录类型声明添加适当的值定义<代码>签名
    应该做同样的事情,但添加镜头功能的签名:

    let structure self str = List.concat (List.map (fun sitem -> match sitem.pstr_desc with
      | Pstr_type tds when tds_with_lenses sitem ->
          sitem :: sitems_for_your_lens_functions
      | _ -> [sitem]) str)
    in  
    let signature self str = List.concat (List.map (fun sgitem -> match sgiitem.psig_desc with
      | Psig_type tds when tds_with_lenses sitem ->
          sgitem :: sgitems_for_your_lens_functions
      | _ -> [sgitem]) str)
    in
    
    super
    self
    与OO相同:
    super
    是您要扩展的原始映射器,
    self
    是扩展的结果。(实际上,对于第一个版本的
    Ast\u mapper
    使用了类而不是记录类型。如果您更喜欢OO样式,您可以使用ppx\u tools package的
    Ast\u mapper\u class
    ,它在OO界面中提供了相同的功能。)。。我想在您的情况下,没有必要使用
    self
    super
    参数

    完成自己的映射程序后,将其设置为
    Ast\u映射程序。应用
    根据输入运行映射程序:

    let () =
      let infile = .. in
      let outfile = .. in
      Ast_mapper.apply ~source:infile ~target:outfile mapper
    

    或多或少,所有的PPX重写器实现都与上述类似。检查几个小型PPX实现肯定有助于您的理解。

    感谢您的帮助,我可以完成这个小型项目。我的理解是AST映射器是延续式递归函数,对吗?对于我使用的程序入口点
    let()=Ast\u mapper.run\u main(fun->lens\u extender default\u mapper)
    。您是否知道使用
    ppx_tools.metaquot
    的工具,我可以学习这些工具以开始使用metaquot?目前我用一个大的Parsetree blob显式地生成了一个Parsetree结构,它并不漂亮!我们在ppx上开了一个纽约OCaml会议。虽然不多,但代码经过了审查。是的,
    Ast\u mapper.run\u main
    是一个更简单的入口点。我个人使用
    apply
    ,因为它更通用
    ppx_工具。metaquot
    非常有助于降低您的工作复杂性。