Another tool in the pattern matching utility belt is discriminated unions. A discriminated union is a type that contains a small number of union cases. Each union case is treated as the same type as the discriminated union, but each case may comprise a different set of properties.
Discriminating Tastes
You could use a discriminated union to represent the different cast type options when opening a socket to send UDP packets: broadcast, unicast, and multicast. If you are opening a socket to broadcast, you only need a port number. If you are opening a socket to unicast, you also need the IP address of the machine you are unicasting to. Finally, if you are opening a socket to multicast, you need an IP address (the multicast group), a port number, and optionally the ID of a network interface card if you want to target a specific network interface.
type SocketMeta = | Broadcast of Port : int | Unicast of Addr : IPAddress * Port : int | Multicast of Addr : IPAddress * Port : int * NicId : string option
Each case can have any number of properties. If there is more than one property, chain them together with tuple syntax using the asterisk (*
).
Now it’s easy to write a function to build a socket based on the meta information:
let buildSocket meta = match meta with | Broadcast port -> // configure for broadcast | Unicast(ip, port) -> // configure for unicast | Multicast(ip, port, nic) -> // configure for multicast let addr = IPAddress.Parse("10.9.01.25") let s = buildSocket (Unicast (addr, 5150))
Discriminated unions are good for times when the number of cases is small, and the number of properties in each case is small. For instance, you have likely worked with a particular discriminated union type many times. Option
is essentially this:
type Option<'T> = | None | Some of 'T
Option
is either some value or nothing at all; that’s it. There is no third alternative. It is very easy to cover all the bases in a fairly short block of code.
Option
also illustrates a couple of additional things about discriminated unions. First, union cases need not have any properties at all. None
stands alone; it requires no properties. Second, naming the properties is optional. Some
is so semantically clear, there is no need to give its single property a name. A type (which happens to be generic in the case of Option
) is always required, but not a name.
Typing Lesson
It turns out that discriminated unions are a specific example of a more general kind of type: an algebraic data type. An algebraic data type is a composite type; that is, it is a combination of other types. An algebraic data type may comprise an infinite set of types or a finite set.
SocketMeta
and Option
both clearly consist of a finite number of types—a small finite number at that. As mentioned already, that’s what makes them so well suited to match expressions.
One reply on “F# Friday – Pattern Matching, Part 3: Discriminated Unions”
[…] F# Friday – Pattern Matching, Part 3: Discriminated Unions -Brad Collins […]