Hi everyone,
I have multiple record types. Some types contain other types in their fields, but usually as parameters to variant constructors. For concreteness, consider
type field = {value : int option;}
type t = {x : [`Field of field | `String of string] option}
It’s not uncommon to see five or so levels of nesting, usually with variant types in between.
It’s common to create a value of type t
and then to want to update its x
field. This update is often done interactively in utop or iocaml. The standard way to do the update would be
let initial : t = {x = Some (`String "x")}
let altered : t = {initial with x = Some (`Field {value = Some 3})}
but that’s way too many keystrokes, especially for interactive use. There’s no reason why the update code shouldn’t be about as short as
let altered : t = initial |> x 3
It’s clear that my intent is to construct the Some
variants, and that I want to construct the Field
variant rather than the String
one.
However I haven’t found a way to get close to that besides generating many specialized updater functions using ppx. Could anyone point me in the direction of a better idea?
I initially looked at lenses, https://github.com/pdonadeo/ocaml-lens but couldn’t get them to work. I could create the lenses:
let lens_field_value : (field, int option) Lens.t = Lens.{
get = (fun f -> f.value);
set = (fun v f -> {f with value=v});
}
let lens_t_x : (t, [`Field of field | `String of string] option) Lens.t = Lens.{
get = (fun thing -> thing.x);
set = (fun x thing -> {thing with x=x});
}
but, because the nesting is separated by variant types, they can’t compose. This doesn’t typecheck:
open Lens.Infix
( lens_t_x |-- lens_field_value)
Even if I could come up with pseudo-lenses that did typecheck, the resulting update code is still much more verbose and complicated-looking than my goal:
let altered : t = (( pseudolens_t_x |-- pseudolens_field_value) ^= (Some 3)) @@ initial
Cheers,
Anand