ocaml - How to write a PPX rewriter generating lenses for records? -
i writing ppx rewriter ease definition of lenses. let me recall casual reader lenses are.
about lenses
a lens associated field of record pair of functions allowing extract record , update it. here example:
module lens = struct type ('a, 'b) t = { : '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 vendor = v }) }
the vendor_lens
allows value of field vendor
in our car
, update – means returning fresh copy of car
differing original value of vendor
car. might @ first sound banal not: since lenses functions, can composed , lenses module filled useful functions. ability compose accessors crucial in complex code bases, eases decoupling abstracting path computation context nested record. refactored getopts , configuration file parser adopt functional interface, makes lenses more relevant – @ least me.
generating lenses
the definition of vendor_lens
above nothing more boilerplate code , there no reason 1 not take advantage of ppx-rewriters let write
type car = { vendor: string; make: string; mileage: int; } [@@with_lenses]
and see automagically definition of lenses need work our car.¹
i decided tackle problem , produce:
a predicate
is_record : parsetree.structure_item -> bool
recognising type record definitions.a function
label_declarations : parsetree.structure_item -> string list
maybe returning list of record declarations record definition – yes, smash 1 , 2 using option.a function
lens_expr : string -> parsetree.structure_item
generating lens definition given field declaration. unfortunately discovered ppx_metaquot alain frisch after wrote function.
it seems me have here essential parts of ppx-rewriter want write. still, how can combine them together?
¹ while searching ppx-rewriter lenses, stumbled on not less 5 blogs or readmes involving same car
structure. recycling example here vile attempt full-time member of selective club of lens-equipped car drivers.
the final goal of ppx project build mapper of type ast_mapper.mapper
.
mapper
large record type , carries mapper functions parsetree
data types, example,
type mapper = { ... structure : mapper -> structure -> structure; signature : mapper -> signature -> signature; ... }
there default mapper ast_mapper.default_mapper
, starting point of mapper: can inherit , override of record members use. lens project, have implement structure
, signature
:
let extend super = let structure self str = ... in let signature self str = ... in { super structure; signature } let mapper = extend default_mapper
your function structure
should scan structure items , add appropriate value definition each record type declaration. signature
should same thing add signatures of lens functions:
let structure self str = list.concat (list.map (fun sitem -> match sitem.pstr_desc | 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 | psig_type tds when tds_with_lenses sitem -> sgitem :: sgitems_for_your_lens_functions | _ -> [sgitem]) str) in
super
, self
same of oo: super
original mapper extending , self
result of extension. (actually first version of ast_mapper
used class instead of record type. if prefer oo style, can use ast_mapper_class
of ppx_tools package, provides same in oo interface.) in anyway.. guess in case, there no need use self
or super
arguments.
once finish own mapper give ast_mapper.apply
run mapper against input:
let () = let infile = .. in let outfile = .. in ast_mapper.apply ~source:infile ~target:outfile mapper
more or less, ppx rewriter implementations above. checking several small ppx implementations surely helps understanding.
Comments
Post a Comment