Verify rewriter and runtime linkage through explicit interfaces
This commit is contained in:
3 files changed
+142
-5
No files matched your search
@@ -1,10 +1,113 @@
|
||||
# fieldglass
|
||||
|
||||
Lenses for reading and updating immutable data in OCaml.
|
||||
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.
|
||||
|
||||
Annotate a record with `[@@fieldglass generate]` to generate field accessors.
|
||||
The `[@@lens generate]` spelling is also accepted.
|
||||
```ocaml
|
||||
type swatch = { colour : string; label : string }
|
||||
[@@fieldglass generate]
|
||||
|
||||
Build with `make`; run the checks with `make test`.
|
||||
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
|
||||
```
|
||||
|
||||
The `_hd` and `_tl` lenses raise `Invalid_argument` on empty lists.
|
||||
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.
|
||||
Reference in new issue
Block a user