Scala’s pattern matching is quite versatile—you can match literals, constants, and arbitrary values with wildcards, tuples, and lists; you can even match based on types and guards. Let’s explore all that, one at a time.
Messages passed between actors are normally String literals, numbers, or tuples. If your message is a literal, you don’t have to do much to match it. Simply type the literal you’d like to match, and you’re done. Suppose we need to determine activities for different days of the week. Assume we get the day as a String and we respond with our activity for that day. Here’s an example of how we can pattern-match the days:
PatternMatching/MatchLiterals.scala | |
| def activity(day: String) { |
| day match { |
| case "Sunday" => print("Eat, sleep, repeat... ") |
| case "Saturday" => print("Hang out with friends... ") |
| case "Monday" => print("...code for fun...") |
| case "Friday" => print("...read a good book...") |
| } |
| } |
| List("Monday", "Sunday", "Saturday").foreach { activity } |
The match is an expression that acts on Any. In this example, we’re using it on a String. It performs pattern matching on the target and invokes the appropriate case expression with the matching pattern value. The output from the code is shown here:
| ...code for fun...Eat, sleep, repeat... Hang out with friends... |
You can directly match against literals and constants. The literals can be different types; the match does not care. However, the type of the target object to the left of match may restrict the type. In this example, since this was of type String, the match could be any string.
In the previous example, we did not handle all possible values of day. If there is a value that is not matched by one of the case expressions, we’ll get a MatchError exception. We can control the values day can take by making the parameter an enum instead of a String. Even then we may not want to handle each day of the week. We can avoid the exception by using a wildcard:
PatternMatching/Wildcard.scala | |
| object DayOfWeek extends Enumeration { |
| val SUNDAY = Value("Sunday") |
| val MONDAY = Value("Monday") |
| val TUESDAY = Value("Tuesday") |
| val WEDNESDAY = Value("Wednesday") |
| val THURSDAY = Value("Thursday") |
| val FRIDAY = Value("Friday") |
| val SATURDAY = Value("Saturday") |
| } |
| |
| def activity(day: DayOfWeek.Value) { |
| day match { |
| case DayOfWeek.SUNDAY => println("Eat, sleep, repeat...") |
| case DayOfWeek.SATURDAY => println("Hang out with friends") |
| case _ => println("...code for fun...") |
| } |
| } |
| |
| activity(DayOfWeek.SATURDAY) |
| activity(DayOfWeek.MONDAY) |
We’ve defined an enumeration for the days of the week. In our activity method, we matched SUNDAY and SATURDAY and let the wildcard, represented by an underscore (_), handle the rest of the days.
If we run the code, we’ll get the match for SATURDAY followed by MONDAY being matched by the wildcard:
| Hang out with friends |
| ...code for fun... |
Matching literals and enumerations is simple. But messages are often not a single literal—they’re often a sequence of values in the form of either tuples or lists. You can use the case expression to match against tuples and lists also. Suppose we are writing a service that needs to receive and process geographic coordinates. The coordinates can be represented as a tuple that we can match like this:
PatternMatching/MatchTuples.scala | |
| def processCoordinates(input: Any) { |
| input match { |
| case (lat, long) => printf("Processing (%d, %d)...", lat, long) |
| case "done" => println("done") |
| case _ => println("invalid input") |
| } |
| } |
| |
| processCoordinates((39, -104)) |
| processCoordinates("done") |
This matches any tuple with two values in it, plus the literal "done". Run the code to see the output:
| Processing (39, -104)...done |
If the argument we send is not a tuple with two elements or doesn’t match "done" then the wildcard will handle it. The printf statement used to print the coordinates has a hidden assumption that the values in the tuple are integers. If they’re not, our code will unfortunately fail at runtime. We can avoid that by providing type information for matches, as you’ll see in the next section.
You can match Lists the same way you matched tuples. Simply provide the elements you care about, and you can leave out the rest using the array explosion symbol (_*):
PatternMatching/MatchList.scala | |
| def processItems(items: List[String]) { |
| items match { |
| case List("apple", "ibm") => println("Apples and IBMs") |
| case List("red", "blue", "white") => println("Stars and Stripes...") |
| case List("red", "blue", _*) => println("colors red, blue,... ") |
| case List("apple", "orange", otherFruits @ _*) => |
| println("apples, oranges, and " + otherFruits) |
| } |
| } |
| |
| processItems(List("apple", "ibm")) |
| processItems(List("red", "blue", "green")) |
| processItems(List("red", "blue", "white")) |
| processItems(List("apple", "orange", "grapes", "dates")) |
In the first and second case, we expected two and three specific items in the List, respectively. In the remaining two cases, we expect two or more items, but the first two items must be as specified. If we need to reference the remaining matching elements, we can place a variable name (like otherFruits) before a special @ symbol as in the last case. The output from the code is shown here:
| Apples and IBMs |
| colors red, blue,... |
| Stars and Stripes... |
| apples, oranges, and List(grapes, dates) |
Sometimes you may want to match based on the type of values. For example, you may want to handle a sequence of, say, Ints differently from how you handle a sequence of Doubles. Scala lets you ask the case statement to match against types, like in this example:
PatternMatching/MatchTypes.scala | |
Line 1 | def process(input: Any) { |
- | input match { |
- | case (a: Int, b: Int) => print("Processing (int, int)... ") |
- | case (a: Double, b: Double) => print("Processing (double, double)... ") |
5 | case msg : Int if (msg > 1000000) => println("Processing int > 1000000") |
- | case msg : Int => print("Processing int... ") |
- | case msg: String => println("Processing string... ") |
- | case _ => printf(s"Can't handle $input... ") |
- | } |
10 | } |
- | |
- | process((34.2, -159.3)) |
- | process(0) |
- | process(1000001) |
15 | process(2.2) |
You see how to specify types for single values and elements of a tuple in the case. Also, you can use guards to constrain the match even more. In addition to matching the pattern, the guard provided by the if clause must be satisfied for the expression following => to evaluate. The output from the code is shown here:
| Processing (double, double)... Processing int... Processing int > 1000000 |
| Can't handle 2.2... |
When writing multiple case expressions, their order matters. Scala will evaluate the case expressions from the top down. So, for example, we shouldn’t swap line numbers 5 and 6 in the code—it’ll result in a warning and a different result since the case with the guard will never be executed.
3.144.222.149