dynamic open records


Orec provides an implementation of "open records" as an interface over universal map. To start using this library, the first step is to chose which namespace to use. The default namespace can be used with

(* Use the default namespace for record field *)
open Orec.Default

or it is possible to define a new namespace with

(* create a new namespace N for record fields *)
module N = Orec.Namespace.Make()
open N

We can then create dynamically a new field

let age : int field = new_field ()

Field are immutable by default, but mutable fields are also available

let position : float mut_field = new_field_mut ()

Creating a new record is as simple as

let r = create [ age ^= 10; position ^= 100. ].

Fields can then be accessed using the record.%{field} syntax

assert ( r.%{age} = Some 10 );;

Unfortunately, the fields presents in r are not tracked by the type system at the moment. For a field of type 'a field, record.%{field} returns an 'a option.

Functional update can be performed using record.%{ field ^= value }

let r2 = r.%{age ^= 20}
assert ( r.%{age} = Some 10 && r2.%{age} = Some 20 &&
r2.%{position} = r.%{position} );;

Note that field can still be added after creation

let tags : 'string list field = new_field()
let r = r.%{ tags ^= ["a tag"] }

Assignment for mutable field follows the natural syntax r.%{field}<- value

let () = r.%{position} <- 20.
assert( r.%{position} = Some 20. && r2.%{position} = Some 20. );;

Mutable fields are shared during functional update. To avoid this behavior, copy the field

let r3= r.%{copy position}
let () = r3.%{position} <- 10.
assert ( r2.%{position} <> Some 10. );;

It is also possible to delete a field,

let r4= r.%{delete position}
assert ( r4.%{position} = None )

or apply a transformation to a field, if the field exists:

let f age = age + 1
let r5= r.%{ fmap f age }
let r5= r.%{ age |= f }
assert ( r5.%{age} = Some 40 )

All these different field updates can be potentially mixed together with the & operator

let r6 = r.%{ age |= f & position ^= 5. & delete tags }

Given a bijection between type 'a and 'b, it is possible to transmute an 'a field to an 'b field

let age_str: string field =
  transmute age {to_ = string_of_int; from = int_of_string }
assert (r.%{age_str} = "10" );;

In some cases, accessing fields through an option type might be unpractical, for instance, external invariants might enforce that a given field is always present. In these situations, it is possible to use exception based fields rather than option-based fields

let name : int exn_field = new_field_exn ()
let error : unit exn_field = new_field_exn ()
let no_one = create [ name ^= "Ulysse" ]
assert( no_one.name = "Ulysse" )
let raise_exception =
    try no_one.%{error} with Not_found -> ()

An exception-based field 'a exn_field returns directly the core type 'a of the field if the field is present and will raise a Not_found exception otherwise. The behavior of all functions and operators defined in Orec is independent from this access mode. In particular, the fmap function always update the record if and only if the underlying field is present in the record.

It is possible to switch from an option-based field to an exception-based by using the tranmute_exn function. See

for the full API