Record lenses for composable accessors and immutable data updates
  • OCaml 81.7%
  • Shell 15.8%
  • Makefile 2.5%
Find a file
2018-09-07 10:19:00 +00:00
.gitignore Define polymorphic lenses and compile the runtime library 2018-09-07 10:00:00 +00:00
case.ml Verify polymorphic updates, name hygiene, and configuration combinations 2018-09-07 10:18:00 +00:00
error.sh Verify polymorphic updates, name hygiene, and configuration combinations 2018-09-07 10:18:00 +00:00
fieldglass.install Traverse record declarations and generate typed field accessors 2018-09-07 10:07:00 +00:00
fieldglass.ml Expose infix viewing, replacement, mapping, and composition 2018-09-07 10:06:00 +00:00
fieldglass.mli Expose infix viewing, replacement, mapping, and composition 2018-09-07 10:06:00 +00:00
fieldglass.opam Define polymorphic lenses and compile the runtime library 2018-09-07 10:00:00 +00:00
Makefile Verify rewriter and runtime linkage through explicit interfaces 2018-09-07 10:19:00 +00:00
META Define polymorphic lenses and compile the runtime library 2018-09-07 10:00:00 +00:00
README.md Verify rewriter and runtime linkage through explicit interfaces 2018-09-07 10:19:00 +00:00
record.ml Disambiguate recursive record groups and share group configuration 2018-09-07 10:16:00 +00:00
rewrite.ml Validate configuration and diagnose unsupported record declarations 2018-09-07 10:17:00 +00:00
runtime.ml Expose infix viewing, replacement, mapping, and composition 2018-09-07 10:06:00 +00:00
smoke.sh Verify rewriter and runtime linkage through explicit interfaces 2018-09-07 10:19:00 +00:00

fieldglass

Generates accessors and composable lenses from OCaml record declarations, allowing a caller to read or replace a nested field through ordinary functions while retaining the surrounding record structure.

type swatch = { colour : string; label : string }
  [@@fieldglass generate]

let grey = { colour = "grey"; label = "sample" }
let blue = set_colour "blue" grey
let labelled = update_label ~f:String.uppercase_ascii grey
let colour = Fieldglass.view _colour blue

For each field, the rewriter emits a getter, a setter prefixed with set_, an updater prefixed with update_, and a getter/setter pair whose name begins with _. A setter takes the replacement value followed by the source record, while an updater applies its labelled ~f argument to the existing field value and constructs a replacement record containing the result.

Generated updates retain the unfocussed fields and perform no assignment to the source record, including when the declaration contains mutable fields. Both [@@fieldglass generate] and [@@lens generate] select this expansion, which produces ordinary OCaml functions that can be used independently of the Fieldglass runtime's composition operations.

Build

Run make to compile the runtime archive and PPX executable, or make test to also check lens operations, generated record accessors, configuration errors, and linkage against an explicit module interface.

To compile a consumer, pass the rewriter through -ppx and link the runtime archive before the source file, as in the following invocation:

ocamlc -ppx ./rewrite fieldglass.cma example.ml -o example

The command OPAMBUILDTEST=true opam pin add fieldglass . builds and tests the package before installing the fieldglass rewriter and runtime library. Use the same OCaml compiler for the rewriter and its consumers, since the PPX exchanges compiler syntax trees and links against the compiler libraries.

Lenses

Fieldglass.lens ~view ~set constructs a pair with representation ('s -> 'a) * ('b -> 's -> 't), where the getter extracts a value from the source and the setter combines a replacement with that source to produce the result. The separate type parameters permit a lens to change the type of its focussed value when the enclosing structure admits that change.

view applies the getter, set applies the setter, and over ~f passes the getter's result through f before supplying it to the setter alongside the original source. With compose outer inner, reading follows the outer getter and then the inner getter; replacement updates the inner structure before passing it back through the outer setter, preserving the surrounding data according to the supplied setters.

Fieldglass.Infix exposes these operations as ^., ^~, and ^%, with ^> composing an outer lens with an inner lens and ^< accepting the same operands in reverse order. The runtime also supplies tuple projections and the identity lens _id, together with _hd and _tl for focussing on a list's head or tail; either list lens raises Invalid_argument when asked to read or update an empty list.

Configuration

Supply labelled options after generate to control the generated names, argument order, and selection of operations, using identifiers or strings for prefixes and argument names. Boolean options accept a bare label as an enabled flag, or an explicit true or false value when the configuration needs to specify the setting directly.

Option Behaviour
~field_prefix:name Add a prefix to each field name.
~field_prefix_from_type Use the record type name as the prefix.
~get_prefix:name Prefix getter names.
~set_prefix:name Replace the set prefix.
~update_prefix:name Replace the update prefix.
~self_arg_first Put the record first in setters and updaters.
~func_named_arg:name Rename the updater's f argument.
~func_no_named_arg Make the updater's function argument positional.
~no_get Omit getters.
~no_set Omit setters.
~no_update Omit updaters.
~no_lens Omit lens pairs.
~just_lens Generate lens pairs without named accessors.

Before applying naming options, the rewriter removes the longest shared field prefix ending at an underscore boundary, so fields such as paint_colour and paint_label produce the base names colour and label. A record containing only one field keeps that field's complete name, while generated lens pairs retain replacement-first setters even when ~self_arg_first changes the argument order of the named accessors.

An annotation attached to a mutually recursive record declaration applies to the entire declaration group, whose options the rewriter processes from left to right with later settings taking precedence. When records share field labels, ~field_prefix_from_type gives their accessors distinct names; the rewriter rejects duplicate generated bindings and repeated options within a single annotation with a compilation error.

Place generation annotations on record declarations in implementations and declare the exported accessor types explicitly in module signatures, since the rewriter emits value bindings and rejects generation annotations in signatures. For private records or records containing universally quantified fields, select ~no_set ~no_update ~no_lens to generate getters without requesting replacement operations that the rewriter does not generate for those declarations.