How to simplify setters for nested records and variants?


#1

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


#2

Did you look at ppx_fields_conv and ppx_variants_conv? They both represent a record field (a variant constructor) as a first-class value that can be passed to a function so that you can write an alterer that is parametrized by a record field.

P.S. I’m not sure, that it will solve your problem, as I actually, didn’t get what you’re looking for, just thought that these two libraries may help.


#3

Thanks for the suggestions. I have used ppx_fields_conv and ppx_variants_conv in the past, but I’m not sure how to use them to create concise setters like I want to do.

It’s hard to communicate the problem without context, yeah. I’ll go ahead and generate the setters, then will post again with a concrete example.


#4

Maybe mapping/copying visitors can be more suitable here. With this library the example can be transformed as follows:

type field = {value : int option;}
and  u = Field of field | String of string
and  t = {x : u option} [@@deriving visitors { variety = "map"}]
let initial = { x = Some (Field { value = Some 4 }) }
let x x' = (object inherit [_] map method visit_int _ _ = x' end)#visit_t ();;
let altered = initial |> x 7

Although this library also has some limitations (e.g. no support for polymorphic variant and GADTs even in limited common cases, limited abilities to define visitors across module boundaries i.e. naming conventions). Also the visitor simultaneously changes all occurrences of similar-typed values, not just one of them, but this can be changed using the environment parameter (in the example it’s just dummy ()).


#5

Sorry for the delay, it took me some time to digest what visitors is doing, in part because I’m not as familiar with the object oriented parts of OCaml.

Visitors was a great suggestion, thanks!. Map visitors offer a handy way to construct functions from a deeply nested type to itself, and as you point out my setters are a special case of what they can do.

I’ll have to experiment a bit to see how the limitations you mention affect the difficulty balance between generating the setters via visitor objects as you have done, and generating them directly. Will update this thread when I’ve done that.


#6

I forgot to update this thread, sorry: OCaml-vega-lite and OCaml dataframes . As I said in the other thread, things are looking better for this library.