How to write a PPX rewriting lens for recording?

I am writing a PPX rewriter to simplify the definition of Lenses . Let me recall to the casual reader what lenses there are.

About lenses

The object associated with the record field is a pair of functions that allow you to retrieve a record and update it. Here is an example:

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 vx -> { x with vendor = v }) } 

vendor_lens allows us to get the value of the vendor field in our car and update it, which means returning a new copy of car that is different from the original, only with the value of vendor car. At first it sounds very commonplace, but it is not: since lenses are essentially functions, they can be assembled, and the Lenses module is filled with useful functions. The ability to assemble accessories is crucial in complex basic codes, since it facilitates separation by abstracting the path from the context calculations to a deeply nested entry. I also recently reorganized Getopts and the parsing configuration file to adopt a functional interface, which makes the lenses even more relevant - at least for me.

Generating lenses

The vendor_lens definition above is nothing more than boilerplate code , and there is no reason why you could not use PPX rewriters to just write

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

and see the automatic detection of the lenses that we need to work with our car.¹

I decided to solve this problem and could produce:

  • is_record : Parsetree.structure_item -> bool predicate is_record : Parsetree.structure_item -> bool recognizing type record definitions.

  • label_declarations : Parsetree.structure_item -> string list function can return a list of record declarations to define a record - yes, we could split 1 and 2 together using the option.

  • the lens_expr : string -> Parsetree.structure_item function lens_expr : string -> Parsetree.structure_item generates a lens definition for the given field declaration. Unfortunately, after I wrote this function, I found ppx_metaquot from Alain Frisch.

It seems to me that I have the essential parts of the PPX rewriting that I want to write. However, how can I combine them together?


¹ When searching for a PPX rewriter for lenses, I came across at least five blogs or READMEs related to the same car structure. The reuse of this example is an abominable attempt to look like a full member of the election club for drivers equipped with a lens.

+4
source share
1 answer

The ultimate goal of your PPX project is to build a mapper of type Ast_mapper.mapper .

mapper is a large record type and contains mapping functions for Parsetree data Parsetree , for example

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

There is mapper Ast_mapper.default_mapper by default, and this is the starting point of your mapper: you can inherit it and override some of the record participants for your use. For a lens design, you must implement structure and signature :

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

Your structure function should check the structure elements and add an appropriate value definition for each declaration of the record type. signature should do the same, but add the signatures of the lens functions:

 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 and self are the same as OO: super is the initial screen that you are expanding, and self is the result of the extension. (Actually, for the first version of Ast_mapper , the class was used instead of the record type. If you prefer the OO style, you can use the Ast_mapper_class package Ast_mapper_class, which provides the same in the OO interface.) Anyway .. I’ll assume that in your case there isn’t need to use self or super arguments.

Once you have finished your own mapper, let Ast_mapper.apply start matching with input:

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

More or less, all PPX rewrite implementations are similar to the ones above. Checking out a few small PPX implementations will surely help you understand.

+4
source