Algebraic data type
A type built from sums (a value is one of several cases) and products (a value has several fields at once), possibly recursively. The name comes from counting: the number of values of a sum is the sum of the counts, and of a product the product, so types obey the laws of algebra.
Write for the number of values of a type. Then
with unit as and an empty variant as . bool is , 'a option is , and there are functions bool -> bool. The laws of arithmetic hold as isomorphisms, and each is a pair of inverse functions:
The last is currying. Recursive types are fixed points: a list is , which unfolds to , a list of length 0, 1, 2, and so on.
A sum of products, a variant whose cases carry only the data that exists in that state, and the distributive law as code.
(* Sum: one of several cases. Product: several fields at once. *)type shape = Circle of float | Rect of float * floatlet area = function Circle r -> Float.pi *. r *. r | Rect (w, h) -> w *. h(* Making illegal states unrepresentable: an address exists only whenconnected, a retry count only while retrying. *)type connection =| Disconnected| Connecting of { attempt : int }| Connected of { address : string }let describe = function| Disconnected -> "disconnected"| Connecting { attempt } -> Printf.sprintf "connecting (attempt %d)" attempt| Connected { address } -> "connected to " ^ address(* A x (B + C) = A x B + A x C, as a pair of inverse functions. *)let distribute : 'a * ('b, 'c) Either.t -> ('a * 'b, 'a * 'c) Either.t = function| a, Left b -> Left (a, b)| a, Right c -> Right (a, c)let factor : ('a * 'b, 'a * 'c) Either.t -> 'a * ('b, 'c) Either.t = function| Left (a, b) -> (a, Left b)| Right (a, c) -> (a, Right c)
Running it.
area (Rect (2., 3.)) -> 6describe -> disconnecteddescribe -> connecting (attempt 2)describe -> connected to 10.0.0.1factor (distribute v) = v for all v -> true
Pattern matching takes a sum apart, and the compiler checks that every case is handled. Leaving one out is a warning, with an example of the value that is missed:
A match on connection that forgets Connecting.
File "adt_warn.ml", lines 6-8, characters 12-25:6 | ............function7 | | Connected _ -> true8 | | Disconnected -> falseWarning 8 [partial-match]: this pattern-matching is not exhaustive.Here is an example of a case that is not matched:Connecting _
That check is what makes a variant like connection useful: when a case is added later, every match that does not handle it is reported. A record of optional fields could represent the same states, plus combinations that should not exist, such as an address while disconnected; the sum type rules them out by construction.
see also
- Polymorphic variantA variant whose constructors, called tags and written with a backquote, exist independently of any type declaration. Their types are sets of tags with bounds: [> `A] means at least `A, [< `A | `B] at most those two, and unification works out unions and intersections of those sets.
- GADTGeneralized Algebraic Data Type: an ADT whose constructors may each refine the type parameter of the value they build, instead of every constructor sharing one polymorphic return type. This lets a single well-typed eval return an int for an Add node and a bool for an Eq node, checked at compile time rather than by an unchecked cast.
- ZipperA data structure with a focus: the subterm at the focus, plus the path back to the root together with everything not on it. Moving the focus and editing at it take constant time, and the type of contexts is the derivative of the structure's type.
- Church encodingRepresenting data as functions in the pure lambda calculus: the numeral n applies its argument n times, true and false choose between two arguments, and a pair waits for a selector. Addition, multiplication and exponentiation are one line each; the predecessor needs a trick and takes time linear in n.
- Scott encodingRepresenting data by its case analysis rather than its fold: a Scott numeral takes what to do for zero and what to do with the predecessor. Pattern matching and the predecessor take one step; iteration needs recursion from outside, such as a fixed-point combinator.
- CurryingTurning a function of several arguments into a function of the first argument that returns a function of the rest: f : A * B -> C becomes curry f : A -> (B -> C). In OCaml and Haskell every function is curried, so applying it to fewer arguments than it takes, partial application, is ordinary.
further reading
- [1]R. M. Burstall, D. B. MacQueen, D. T. Sannella, “HOPE: an experimental applicative language”, LISP Conference (1980).
- [2]B. C. Pierce, Types and Programming Languages, ch. 11, MIT Press (2002).
- [3]C. McBride, “The derivative of a regular type is its type of one-hole contexts” (2001).