In a recent post, I described some of the problems associated with OCaml’s built in polymorphic comparison functions. So, if you want to avoid OCaml’s polymorphic compare, what are your options? One approach is to simply write one’s own comparison functions explicitly. Unfortunately, it’s hard to do that cleanly. Consider the case of writing a comparison function for a simple variant type:

type t =
| Foo of Float.t
| Bar of Int.t
| Snoo of String.t

Your first approach to writing a comparison function might be something like this:

let tag_to_int = function
| Foo _ -> 0
| Bar _ -> 1
| Snoo _ -> 2

let compare x y =
  let x = (tag_to_int x) (tag_to_int y) in
  if x <> 0 then x
  else match x, y with
  | Foo x, Foo y -> x y
  | Bar x, Bar y -> x y
  | Snoo x, Snoo y -> x y
  | _, _ -> assert false

This is decent, but it unfortunately uses a fragile match at the end, which means if you extend t by adding another variant, the compiler will not warn you to extend the math in compare (although it will warn you about extending the match in tag_to_int). We can make the compare function a little better by getting rid of the fragile match:

let compare x y =
  match x, y with
  | Foo x, Foo y -> x y
  | Bar x, Bar y -> x y
  | Snoo x, Snoo y -> x y
  | (Foo _ | Bar _ | Snoo _), _ -> (tag_to_int x) (tag_to_int y)

This produces better compiler errors, but it’s still painful and error-prone. The situation is even worse with records, since you don’t get any kind of exhaustiveness check at all. You can get the right kind of checks by using the pa_fields macro I mentioned earlier, but the resulting code is not nearly as efficient as a direct implementation.

Given the pain involved in writing manual comparison functions, I’ve come to think that the right solution is to use macros for generating these functions, in the style of sexplib. As with sexplib, this makes it easy to override the standard behavior for a particular type in a lightweight way, by simply writing a custom function in the few places where that is necessary. The rest of the time, the macros can do the work. I suspect that such a macro will make its way into a future release of core.