views:

65

answers:

3

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.

+1  A: 

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 functinos 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
  ...
Victor Nicollet
Note that, precisely because references are first-class values, the "references instead of mutable fields" approach completely changes the meaning of the `{ existing_record with a_record_uuid = ... }` syntax. Just for that reason I would prefer to use setter functions.
Pascal Cuoq
@Pascal: indeed. On the other hand, if you have mutable fields, that syntax should be used very carefully anyway.
Victor Nicollet
This seems like a rather complex answer to a simple question. Existential types do not play a serious role in this in practice. The one thing that is missing in OCaml is first-class record fields, although those can be added easily enough using existing camlp4 macros. In any case, this is a small syntactic convenience.
zrr
A: 

I'm generally unsatisfied with writing code like this

A sign of good taste, if you ask me :-)


I know of no magic, but I think the best path is to split the boilerplate:

  1. One boilerplate setter function for each mutable field. May be useful in different contexts.

  2. One data structure to map integer codes to "what to do with this field"

You can implement your record scanner using a table instead of a function. A suggestive example appears below. The difference between gets and gett is a real kicker here. In what follows,

  • sf stands for "string field"
  • tf stands for "time field"
  • eor stands for "end of record"

I've made up tabulate and lookup to suit my example; use whatever data structure is efficient.

let sf set a c =     let c, s = gets() in (set a s; `More_record c)
let tf set a c =     let c, s = gett() in (set a t; `More_record c)
let eor    a c =     `End_of_record c

let fields = tabulate
  [ 0x01, sf a_record_uuid
  ; 0x02, sf a_group
  ; ...
  ; 0x07, tf a_creation_time
  ; ...
  ]

let load_record_field cursor gets geti gett a code = lookup fields code cursor a
Norman Ramsey
+2  A: 

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.

zrr
Embarrassingly simple.
mbac32768