Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Factoring product type assignments in OCaml

I'm generally unsatisfied with writing code like this:

let load_record_field cursor gets geti gett a = function
  | 0x01 -> let c, s = gets () in (a.a_record_uuid <- s; `More_record c)
  | 0x02 -> let c, s = gets () in (a.a_group <- s; `More_record c)
  | 0x03 -> let c, s = gets () in (a.a_title <- s; `More_record c)
  | 0x04 -> let c, s = gets () in (a.a_username <- s; `More_record c)
  | 0x07 -> let c, t = gett () in (a.a_creation_time <- t; `More_record c)
  .
  .
  .
  | 0xFF -> `End_of_record cursor

I've minimized the boilerplate, but I was wondering if there was any OCaml magic that would let me completely eliminate it.

like image 354
mbac32768 Avatar asked Oct 25 '22 19:10

mbac32768


2 Answers

This is dead simple: just use a closure to do the setting, and write a function to abstract out the boilerplate

let load_record_field cursor gets geti gett a x =
  let frob get set =
     let (c,s) = get () in
     set s; `More_record c
  in
  function
  | 0x01 -> frob gets (fun s -> a.a_record_uuid <- s)
  | 0x02 -> frob gets (fun s -> a.a_group <- s)
  | 0x03 -> frob gett (fun s -> a.a_title <- s)
  ...

and so on.

You can make this even better if you use a macro package like Jane Street's fieldslib. That generates first-class fields, along with automatically generated setters and getters. This would mean that you wouldn't have to construct the closure each time by hand.

like image 123
zrr Avatar answered Nov 13 '22 19:11

zrr


The shortest you could get away with in theory is:

frobnicate (function 
| 0x01 -> gets , a_record_uuid 
| 0x02 -> gets , a_group 
  ...
)

Of course, you will be foiled by OCaml because 1° there are no "pointer to member" constructs in Objective Caml, so you would have to write fun a s -> a.a_record_uuid <- s instead of a_record_uuid (at the very least) and 2° the type system does not fully support existential quantification, so that the return type of the function cannot be the expected:

exists 'a. int -> (unit -> record * 'a) * ('a -> record -> unit)

I guess you could solve 1° by having named functions for setting values in a record, if you happen to do that often enough:

type complex = { re : int ; im : int }
let re r c = { c with re = r }
let im r c = { c with im = i }

It's a bit unorthodox, I guess, but it usually pays off later on because I tend to use them in most functional situations. You could create the equivalent in imperative style, or you could accept the overhead of a function (it only adds around 20 characters).

As or 2°, it can be solved by hiding the existential quantifier in a function:

let t e read write = let c, x = read () in write x e ; `More_record c

This would let you go down to:

let t = t a in
match 
  | 0x01 -> t gets a_record_uuid 
  | 0x02 -> t gets a_title
  ...

I wouldn't be surprised if CamlP4 supported some kind of sugar for assignment functions. In the mean time, if you use references instead of mutable fields, you can shorten this up (because references are first class values, fields are not):

let t read reference = let c, x = read () in reference := x ; `More_record c

match 
  | 0x01 -> t gets a.a_record_uuid
  ...
like image 43
Victor Nicollet Avatar answered Nov 13 '22 21:11

Victor Nicollet