Another tool in the pattern matching utility belt is disjoint unions. A disjoint union is a type that contains a small number of union cases. Each union case is treated as the same type as the disjoint union, but each case may comprise a different set of properties. You can build disjoint unions with Scala traits and case classes.
Rockin’ Disjoint (Unions)
You could use a disjoint 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.
sealed trait SocketMeta case class Broadcast(port: Int) extends SocketMeta case class Unicast(addr: InetAddress, port: Int) extends SocketMeta case class Multicast(addr: InetAddress, port: Int, nicId: Option[String]) extends SocketMeta
The SocketMeta
trait serves as your disjoint union type. Define each union case as a case class that mixes in the SocketMeta
trait. As you can see, each union case can have any number of properties, according to the familiar case class constructor syntax. Limit the number of union cases by marking SocketMeta
as sealed
. That way, all SocketMeta
union cases must reside in this file.
Now it’s easy to write a function to build a socket based on the meta information:
def buildSocket(meta: SocketMeta) = { meta match { case Broadcast(port) => // configure for broadcast case Unicast(ip, port) => // configure for unicast case Multicast(ip, port, nic) => // configure for multicast } } val s = buildSocket(Broadcast(5150))
Disjoint 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 disjoint union type many times. Option
is essentially this:
sealed trait Option[+A] case object None extends Option[Nothing] case class Some[+A](get: A) extends Option[A]
(I know I have not covered case objects explicitly, but if you get the concept of a case class, case objects are pretty straightforward, too. They are singleton objects that you can use in pattern matching.)
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 one more thing about disjoint unions: union cases need not have any properties at all. None
stands alone; it requires no properties.
Typing Lesson
It turns out that disjoint 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.