Comprehensive Matching

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.

Matching Literals and Constants

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.

Matching a Wildcard

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 Tuples and Lists

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)

Matching with Types and Guards

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)... "​)
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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.222.149