Scala Saturday – Pattern Matching, Part 3: More Case Classes

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.