114 lines
5.4 KiB
Markdown
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.
|