Files

114 lines
5.4 KiB
Markdown

# 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.
```ocaml
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:
```sh
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.