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

时间:2015-08-17 00:49:24

标签: ocaml metaprogramming

我正在编写PPX重写器以简化Lenses的定义。让我回忆一下随便的读者镜头是什么。

关于镜片

与记录字段相关联的镜头是一对允许提取记录并更新记录的功能。这是一个例子:

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_lens允许我们在vendor中获取字段car的值并更新它 - 这意味着返回与car不同的新副本原件只有vendor车的价值。这听起来可能非常平庸,但事实并非如此:因为镜头本质上是功能,它们可以组成,Lenses模块充满了有用的功能。组合访问器的能力在复杂的代码库中至关重要,因为它通过将路径从计算上下文抽象到深度嵌套的记录来简化解耦。我最近还重构了我的Getoptsconfiguration file parser以采用功能界面,这使得镜头更具相关性 - 至少对我而言。

生成镜片

上面vendor_lens的定义只不过是boilerplate code,并且没有理由不能利用PPX重写器来让我们简单地写一下

type car = {
  vendor: string;
  make: string;
  mileage: int;
} [@@with_lenses]

并看到我们需要与我们的汽车一起使用的镜片的自动定义.¹

我决定解决这个问题并且可以产生:

  1. 谓词is_record : Parsetree.structure_item -> bool识别类型记录定义。

  2. 函数label_declarations : Parsetree.structure_item -> string list可能返回记录定义的记录声明列表 - 是的,我们可以使用选项一起粉碎1和2。

    < / LI>
  3. 函数lens_expr : string -> Parsetree.structure_item生成给定字段声明的镜头定义。不幸的是,在我写完这个函数之后,我发现了Alain Frisch的{​​{3}}。

  4. 在我看来,我在这里有我想写的PPX重写器的基本部分。不过,我怎样才能将它们组合在一起?

    ¹在搜索镜头的PPX重写器时,我偶然发现了不少于五个涉及同一car结构的博客或自述文件。回收这个例子是一个卑鄙的尝试,看起来像装备镜头的汽车司机的选择性俱乐部的全职成员。

1 个答案:

答案 0 :(得分:4)

PPX项目的最终目标是构建Ast_mapper.mapper类型的映射器。

mapper是一个大型记录类型,带有Parsetree数据类型的映射函数,例如,

type mapper = { 
  ...
  structure : mapper -> structure -> structure;
  signature : mapper -> signature -> signature;
  ...
}

有一个默认的映射器Ast_mapper.default_mapper,这是映射器的起点:您可以继承它并覆盖一些记录成员供您使用。对于镜头项目,您必须实施structuresignature

let extend super =
  let structure self str = ... in
  let signature self str = ... in
  { super with structure; signature }

let mapper = extend default_mapper

您的函数structure应扫描结构项并为每个记录类型声明添加适当的值定义。 signature应该做同样的事情,但添加镜头功能的签名:

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

superself与OO相同:super是您要扩展的原始映射器,self是扩展的结果。 (实际上,Ast_mapper的第一个版本使用的是类而不是记录类型。如果您更喜欢OO样式,则可以使用ppx_tools包的Ast_mapper_class,它在OO界面中提供相同的功能。)在无论如何..我想在你的情况下,没有必要使用selfsuper参数。

完成自己的映射器后,给它Ast_mapper.apply根据输入运行映射器:

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

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