Imagine you’re a new age librarian, tasked with cataloging information on the Internet. There are many types of information. Here are some significant ones:
case class Tweet(user: String, number: Long)
case class Xkcd(number: Int)
case class HackerNews(item: Int, points: Int)
Here are some examples:
// https://twitter.com/PLT_Borat/status/248038616654299136
val tweet = Tweet("PLT_Borat", 248038616654299136L)
// http://xkcd.com/1316/
val comic = Xkcd(1316)
// https://news.ycombinator.com/item?id=8169367
val news = HackerNews(8169367, 305)
Let’s write a function called getURL
that maps these items to their URLs.
Here is how do it in Scala:
def getURL(item: Any): String = item match {
case Tweet(user, number) => "https://twitter.com/" + user + "/status/" + number
case Xkcd(number) => "http://xkcd.com/" + number
case HackerNews(item, _) => "http://news.ycombinator.com/item?id=" + item
case _ => error("not a library item")
}
This definition is unsatisfactory. getURL
takes values of Any
type. So,
it is really easy to get a runtime error:
scala> getURL("hello")
java.lang.RuntimeException: not a library item
at scala.sys.package$.error(package.scala:27)
at cmpsci220.package$$anonfun$1.apply(package.scala:13)
at cmpsci220.package$$anonfun$1.apply(package.scala:13)
at .getURL(<console>:38)
... 33 elided
To eliminate this kind of error, we need an Item
type:
sealed trait Item
case class Tweet(user: String, number: Long) extends Item
case class Xkcd(number: Int) extends Item
case class HackerNews(item: Int, points: Int) extends Item
A trait in Scala is like an interface. They’re also much more versatile than interfaces, but we’ll get into that later.
Now, we can rewrite getURL
, restricting argument type:
def getURL(item: Item): String = item match {
case Tweet(user, number) => "https://twitter.com/" + user + "/status/" + number
case Xkcd(number) => "http://xkcd.com/" + number
case HackerNews(item, _) => "http://news.ycombinator.com/item?id=" + item
}
This time, applying getURL
to a non-item produces a type error as expected:
scala> item("hello")
<console>:43: error: type mismatch;
found : String("hello")
required: Item
getURL("hello")
A really nice feature of match
is that it checks to ensure you’ve handled all
cases. For example, suppose we forgot to write the HackerNews case. Scala
prints the following error:
<console>:18: warning: match may not be exhaustive.
It would fail on the following input: HackerNews(_, _)
def getURL(item: Item): String = item match {
^
error: No warnings can be incurred under -Xfatal-warnings.
You can use match
to also match concrete values. For example, here is
variant of getURL that censors a particular tweet from PLT Borat:
def getURL(item: Item): String = item match {
case Tweet("PLT_Borat", 301113983723794432L) => "http://disney.com"
case Tweet(user, number) => "https://twitter.com/" + user + "/status/" + number
case Xkcd(number) => "http://xkcd.com/" + number
case HackerNews(item, _) => "http://news.ycombinator.com/item?id=" + item
}
Imagine you’d added the censoring line, but accidentally removed the line that handles all other Tweets. Again, Scala will catch the error:
<console>:62: warning: match may not be exhaustive.
It would fail on the following inputs:
Tweet("PLT_Borat", (x: Long forSome x not in 301113983723794432L)),
Tweet((x: String forSome x not in "PLT_Borat"), 301113983723794432L),
Tweet((x: String forSome x not in "PLT_Borat"), _),
Tweet(_, (x: Long forSome x not in 301113983723794432L))
def getURL(item: Item): String = item match {
^
error: No warnings can be incurred under -Xfatal-warnings.
It is a long error message. But, if you read it carefully, you’ll see that it is very precisely describing exactly the cases that are missing.
Read Chapter 15, “Case Classes and Pattern Matching” from Programming in Scala. You can stop after reading Section 15.5.