Discriminated unions or sum types are a natural way to model logical OR. Often you have a property that distributes over OR. Say, in F# (used throughout the article, though the ideas should apply equally well to any ML), you can write a combinator of the type:
P<'T1> → P<'T2> → P<Choice<'T1,'T2>>
How to go from here to a nice set of combinators that would handle an arbitrary union? This question has been on my mind for a while, and finally I have an acceptable solution.
As a disclaimer, at the level of theory the question is completely trivial. The interest is in how to find a user-friendly ML interface. Even this, I suspect, has been solved before, and at a much more general level. I am aware for instance of Vesa Karvonen's
Generics for the working ML'er, and a more recent Oleg Kiselyov's
presentation of generics in OCaml. I am looking here at a much simpler setting, hopefully taking it to a more accessible, for-dummies-like-myself level.
Suppose you are designing binary format combinators to let users of your library construct values of the form:
type Format<'T> =
{
Read : BinaryReader → 'T
Write : BinaryWriter → 'T → unit
}
Given an arbitrary union type and some primitive Format values, the user should be able to compose them. This involves projecting from sub-types to the parent union type, and matching backwards. After some experimentation, the design I have looks like this:
type U =
| A of int
| B of float
| C of string
let UFormat =
Union (fun a b c x →
match x with
| A x → a x
| B x → b x
| C x → c x)
<< Case A IntFormat
<< Case B FloatFormat
<< Case C StringFormat
<| End
That's pretty much it. The magic is in the type of Case combinator that accumulates types so that the Union combinator can present N-way pattern matching as a single reasonably convenient to write function. Full code: