- •Contents
- •List of Figures
- •List of Tables
- •List of Listings
- •Foreword
- •Foreword to the First Edition
- •Acknowledgments
- •Introduction
- •A Scalable Language
- •A language that grows on you
- •What makes Scala scalable?
- •Why Scala?
- •Conclusion
- •First Steps in Scala
- •Conclusion
- •Next Steps in Scala
- •Conclusion
- •Classes and Objects
- •Semicolon inference
- •Singleton objects
- •A Scala application
- •Conclusion
- •Basic Types and Operations
- •Some basic types
- •Literals
- •Operators are methods
- •Arithmetic operations
- •Relational and logical operations
- •Bitwise operations
- •Object equality
- •Operator precedence and associativity
- •Rich wrappers
- •Conclusion
- •Functional Objects
- •Checking preconditions
- •Self references
- •Auxiliary constructors
- •Method overloading
- •Implicit conversions
- •A word of caution
- •Conclusion
- •Built-in Control Structures
- •If expressions
- •While loops
- •For expressions
- •Match expressions
- •Variable scope
- •Conclusion
- •Functions and Closures
- •Methods
- •Local functions
- •Short forms of function literals
- •Placeholder syntax
- •Partially applied functions
- •Closures
- •Special function call forms
- •Tail recursion
- •Conclusion
- •Control Abstraction
- •Reducing code duplication
- •Simplifying client code
- •Currying
- •Writing new control structures
- •Conclusion
- •Composition and Inheritance
- •A two-dimensional layout library
- •Abstract classes
- •Extending classes
- •Invoking superclass constructors
- •Polymorphism and dynamic binding
- •Using composition and inheritance
- •Heighten and widen
- •Putting it all together
- •Conclusion
- •How primitives are implemented
- •Bottom types
- •Conclusion
- •Traits
- •How traits work
- •Thin versus rich interfaces
- •Example: Rectangular objects
- •The Ordered trait
- •Why not multiple inheritance?
- •To trait, or not to trait?
- •Conclusion
- •Packages and Imports
- •Putting code in packages
- •Concise access to related code
- •Imports
- •Implicit imports
- •Package objects
- •Conclusion
- •Assertions and Unit Testing
- •Assertions
- •Unit testing in Scala
- •Informative failure reports
- •Using JUnit and TestNG
- •Property-based testing
- •Organizing and running tests
- •Conclusion
- •Case Classes and Pattern Matching
- •A simple example
- •Kinds of patterns
- •Pattern guards
- •Pattern overlaps
- •Sealed classes
- •The Option type
- •Patterns everywhere
- •A larger example
- •Conclusion
- •Working with Lists
- •List literals
- •The List type
- •Constructing lists
- •Basic operations on lists
- •List patterns
- •First-order methods on class List
- •Methods of the List object
- •Processing multiple lists together
- •Conclusion
- •Collections
- •Sequences
- •Sets and maps
- •Selecting mutable versus immutable collections
- •Initializing collections
- •Tuples
- •Conclusion
- •Stateful Objects
- •What makes an object stateful?
- •Reassignable variables and properties
- •Case study: Discrete event simulation
- •A language for digital circuits
- •The Simulation API
- •Circuit Simulation
- •Conclusion
- •Type Parameterization
- •Functional queues
- •Information hiding
- •Variance annotations
- •Checking variance annotations
- •Lower bounds
- •Contravariance
- •Object private data
- •Upper bounds
- •Conclusion
- •Abstract Members
- •A quick tour of abstract members
- •Type members
- •Abstract vals
- •Abstract vars
- •Initializing abstract vals
- •Abstract types
- •Path-dependent types
- •Structural subtyping
- •Enumerations
- •Case study: Currencies
- •Conclusion
- •Implicit Conversions and Parameters
- •Implicit conversions
- •Rules for implicits
- •Implicit conversion to an expected type
- •Converting the receiver
- •Implicit parameters
- •View bounds
- •When multiple conversions apply
- •Debugging implicits
- •Conclusion
- •Implementing Lists
- •The List class in principle
- •The ListBuffer class
- •The List class in practice
- •Functional on the outside
- •Conclusion
- •For Expressions Revisited
- •For expressions
- •The n-queens problem
- •Querying with for expressions
- •Translation of for expressions
- •Going the other way
- •Conclusion
- •The Scala Collections API
- •Mutable and immutable collections
- •Collections consistency
- •Trait Traversable
- •Trait Iterable
- •Sets
- •Maps
- •Synchronized sets and maps
- •Concrete immutable collection classes
- •Concrete mutable collection classes
- •Arrays
- •Strings
- •Performance characteristics
- •Equality
- •Views
- •Iterators
- •Creating collections from scratch
- •Conversions between Java and Scala collections
- •Migrating from Scala 2.7
- •Conclusion
- •The Architecture of Scala Collections
- •Builders
- •Factoring out common operations
- •Integrating new collections
- •Conclusion
- •Extractors
- •An example: extracting email addresses
- •Extractors
- •Patterns with zero or one variables
- •Variable argument extractors
- •Extractors and sequence patterns
- •Extractors versus case classes
- •Regular expressions
- •Conclusion
- •Annotations
- •Why have annotations?
- •Syntax of annotations
- •Standard annotations
- •Conclusion
- •Working with XML
- •Semi-structured data
- •XML overview
- •XML literals
- •Serialization
- •Taking XML apart
- •Deserialization
- •Loading and saving
- •Pattern matching on XML
- •Conclusion
- •Modular Programming Using Objects
- •The problem
- •A recipe application
- •Abstraction
- •Splitting modules into traits
- •Runtime linking
- •Tracking module instances
- •Conclusion
- •Object Equality
- •Equality in Scala
- •Writing an equality method
- •Recipes for equals and hashCode
- •Conclusion
- •Combining Scala and Java
- •Using Scala from Java
- •Annotations
- •Existential types
- •Using synchronized
- •Compiling Scala and Java together
- •Conclusion
- •Actors and Concurrency
- •Trouble in paradise
- •Actors and message passing
- •Treating native threads as actors
- •Better performance through thread reuse
- •Good actors style
- •A longer example: Parallel discrete event simulation
- •Conclusion
- •Combinator Parsing
- •Example: Arithmetic expressions
- •Running your parser
- •Basic regular expression parsers
- •Another example: JSON
- •Parser output
- •Implementing combinator parsers
- •String literals and regular expressions
- •Lexing and parsing
- •Error reporting
- •Backtracking versus LL(1)
- •Conclusion
- •GUI Programming
- •Panels and layouts
- •Handling events
- •Example: Celsius/Fahrenheit converter
- •Conclusion
- •The SCells Spreadsheet
- •The visual framework
- •Disconnecting data entry and display
- •Formulas
- •Parsing formulas
- •Evaluation
- •Operation libraries
- •Change propagation
- •Conclusion
- •Scala Scripts on Unix and Windows
- •Glossary
- •Bibliography
- •About the Authors
- •Index
Section 15.2 |
Chapter 15 · Case Classes and Pattern Matching |
314 |
expr match {
case BinOp(op, left, right) => println(expr +" is a binary operation")
case _ =>
}
Listing 15.3 · A pattern match with an empty “default” case.
15.2 Kinds of patterns
The previous example showed several kinds of patterns in quick succession. Now take a minute to look at each.
The syntax of patterns is easy, so do not worry about that too much. All patterns look exactly like the corresponding expression. For instance, given the hierarchy of Listing 15.1, the pattern Var(x) matches any variable expression, binding x to the name of the variable. Used as an expression, Var(x)—exactly the same syntax—recreates an equivalent object, assuming x is already bound to the variable’s name. Since the syntax of patterns is so transparent, the main thing to pay attention to is just what kinds of patterns are possible.
Wildcard patterns
The wildcard pattern (_) matches any object whatsoever. You have already seen it used as a default, catch-all alternative, like this:
expr match {
case BinOp(op, left, right) => println(expr +" is a binary operation")
case _ =>
}
Wildcards can also be used to ignore parts of an object that you do not care about. For example, the previous example does not actually care what the elements of a binary operation are. It just checks whether it is a binary operation at all. Thus the code can just as well use the wildcard pattern for the elements of the BinOp, as shown in Listing 15.4:
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 15.2 |
Chapter 15 · Case Classes and Pattern Matching |
315 |
expr match {
case BinOp(_, _, _) => println(expr +" is a binary operation")
case _ => println("It's something else")
}
Listing 15.4 · A pattern match with wildcard patterns.
Constant patterns
A constant pattern matches only itself. Any literal may be used as a constant. For example, 5, true, and "hello" are all constant patterns. Also, any val or singleton object can be used as a constant. For example, Nil, a singleton object, is a pattern that matches only the empty list. Listing 15.5 shows some examples of constant patterns:
def describe(x: Any) = x match { case 5 => "five"
case true => "truth" case "hello" => "hi!"
case Nil => "the empty list" case _ => "something else"
}
Listing 15.5 · A pattern match with constant patterns.
Here is how the pattern match shown in Listing 15.5 looks in action:
scala> describe(5)
res6: java.lang.String = five
scala> describe(true)
res7: java.lang.String = truth
scala> describe("hello") res8: java.lang.String = hi!
scala> describe(Nil)
res9: java.lang.String = the empty list
scala> describe(List(1,2,3))
res10: java.lang.String = something else
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 15.2 |
Chapter 15 · Case Classes and Pattern Matching |
316 |
Variable patterns
A variable pattern matches any object, just like a wildcard. Unlike a wildcard, Scala binds the variable to whatever the object is. You can then use this variable to act on the object further. For example, Listing 15.6 shows a pattern match that has a special case for zero, and a default case for all other values. The default case uses a variable pattern so that it has a name for the value, no matter what it is.
expr match {
case 0 => "zero"
case somethingElse => "not zero: "+ somethingElse
}
Listing 15.6 · A pattern match with a variable pattern.
Variable or constant?
Constant patterns can have symbolic names. You saw this already when we used Nil as a pattern. Here is a related example, where a pattern match involves the constants E (2.71828. . . ) and Pi (3.14159. . . ):
scala> import math.{E, Pi} import math.{E, Pi}
scala> E match {
case Pi => "strange math? Pi = "+ Pi case _ => "OK"
}
res11: java.lang.String = OK
As expected, E does not match Pi, so the “strange math” case is not used. How does the Scala compiler know that Pi is a constant imported from
scala.math, and not a variable that stands for the selector value itself? Scala uses a simple lexical rule for disambiguation: a simple name starting with a lowercase letter is taken to be a pattern variable; all other references are taken to be constants. To see the difference, create a lowercase alias for pi and try with that:
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 15.2 |
Chapter 15 · Case Classes and Pattern Matching |
317 |
scala> val pi = math.Pi
pi: Double = 3.141592653589793
scala> E match {
case pi => "strange math? Pi = "+ pi
}
res12: java.lang.String = strange math? Pi = 2.718281828459045
Here the compiler will not even let you add a default case at all. Since pi is a variable pattern, it will match all inputs, and so no cases following it can be reached:
scala> E match {
case pi => "strange math? Pi = "+ pi case _ => "OK"
}
<console>:9: error: unreachable code case _ => "OK"
ˆ
If you need to, you can still use a lowercase name for a pattern constant, using one of two tricks. First, if the constant is a field of some object, you can prefix it with a qualifier. For instance, pi is a variable pattern, but this.pi or obj.pi are constants even though they start with lowercase letters. If that does not work (because pi is a local variable, say), you can alternatively enclose the variable name in back ticks. For instance, `pi` would again be interpreted as a constant, not as a variable:
scala> E match {
case `pi` => "strange math? Pi = "+ pi case _ => "OK"
}
res14: java.lang.String = OK
As you can see, the back-tick syntax for identifiers is used for two different purposes in Scala to help you code your way out of unusual circumstances. Here you see that it can be used to treat a lowercase identifier as a constant in a pattern match. Earlier on, in Section 6.10, you saw that it can also be used to treat a keyword as an ordinary identifier, e.g., writing Thread.`yield`() treats yield as an identifier rather than a keyword.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 15.2 |
Chapter 15 · Case Classes and Pattern Matching |
318 |
Constructor patterns
Constructors are where pattern matching becomes really powerful. A constructor pattern looks like “BinOp("+", e, Number(0))”. It consists of a name (BinOp) and then a number of patterns within parentheses: "+", e, and Number(0). Assuming the name designates a case class, such a pattern means to first check that the object is a member of the named case class, and then to check that the constructor parameters of the object match the extra patterns supplied.
These extra patterns mean that Scala patterns support deep matches. Such patterns not only check the top-level object supplied, but also check the contents of the object against further patterns. Since the extra patterns can themselves be constructor patterns, you can use them to check arbitrarily deep into an object. For example, the pattern shown in Listing 15.7 checks that the top-level object is a BinOp, that its third constructor parameter is a Number, and that the value field of that number is 0. This pattern is one line long yet checks three levels deep.
expr match {
case BinOp("+", e, Number(0)) => println("a deep match") case _ =>
}
Listing 15.7 · A pattern match with a constructor pattern.
Sequence patterns
You can match against sequence types like List or Array just like you match against case classes. Use the same syntax, but now you can specify any number of elements within the pattern. For example, Listing 15.8 shows a pattern that checks for a three-element list starting with zero:
expr match {
case List(0, _, _) => println("found it") case _ =>
}
Listing 15.8 · A sequence pattern with a fixed length.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 15.2 |
Chapter 15 · Case Classes and Pattern Matching |
319 |
If you want to match against a sequence without specifying how long it can be, you can specify _* as the last element of the pattern. This funnylooking pattern matches any number of elements within a sequence, including zero elements. Listing 15.9 shows an example that matches any list that starts with zero, regardless of how long the list is.
expr match {
case List(0, _*) => println("found it") case _ =>
}
Listing 15.9 · A sequence pattern with an arbitrary length.
Tuple patterns
You can match against tuples, too. A pattern like (a, b, c) matches an arbitrary 3-tuple. An example is shown in Listing 15.10:
def tupleDemo(expr: Any) = expr match {
case (a, b, c) => println("matched "+ a + b + c) case _ =>
}
Listing 15.10 · A pattern match with a tuple pattern.
If you load the tupleDemo method shown in Listing 15.10 into the interpreter, and pass to it a tuple with three elements, you’ll see:
scala> tupleDemo(("a ", 3, "-tuple")) matched a 3-tuple
Typed patterns
You can use a typed pattern as a convenient replacement for type tests and type casts. Listing 15.11 shows an example:
Here are a few examples of using the generalSize method in the Scala interpreter:
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 15.2 |
Chapter 15 · Case Classes and Pattern Matching |
320 |
def generalSize(x: Any) = x match { case s: String => s.length
case m: Map[_, _] => m.size case _ => -1
}
Listing 15.11 · A pattern match with typed patterns.
scala> generalSize("abc") res16: Int = 3
scala> generalSize(Map(1 -> 'a', 2 -> 'b')) res17: Int = 2
scala> generalSize(math.Pi) res18: Int = -1
The generalSize method returns the size or length of objects of various types. Its argument is of type Any, so it could be any value. If the argument is a String, the method returns the string’s length. The pattern “s: String” is a typed pattern; it matches every (non-null) instance of String. The pattern variable s then refers to that string.
Note that, even though s and x refer to the same value, the type of x is Any, but the type of s is String. So you can write s.length in the alternative expression that corresponds to the pattern, but you could not write x.length, because the type Any does not have a length member.
An equivalent but more long-winded way that achieves the effect of a match against a typed pattern employs a type test followed by a type cast. Scala uses a different syntax than Java for these. To test whether an expression expr has type String, say, you write:
expr.isInstanceOf[String]
To cast the same expression to type String, you use:
expr.asInstanceOf[String]
Using a type test and cast, you could rewrite the first case of the previous match expression as shown in Listing 15.12.
The operators isInstanceOf and asInstanceOf are treated as predefined methods of class Any which take a type parameter in square brackets.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 15.2 |
Chapter 15 · Case Classes and Pattern Matching |
321 |
if (x.isInstanceOf[String]) { val s = x.asInstanceOf[String] s.length
} else ...
Listing 15.12 · Using isInstanceOf and asInstanceOf (poor style).
In fact, x.asInstanceOf[String] is a special case of a method invocation with an explicit type parameter String.
As you will have noted by now, writing type tests and casts is rather verbose in Scala. That’s intentional, because it is not encouraged practice. You are usually better off using a pattern match with a typed pattern. That’s particularly true if you need to do both a type test and a type cast, because both operations are then rolled into a single pattern match.
The second case of the previous match expression contains the type pattern “m: Map[_, _]”. This pattern matches any value that is a Map of some arbitrary key and value types and lets m refer to that value. Therefore, m.size is well typed and returns the size of the map. The underscores in the type pattern are like wildcards in other patterns. You could have also used (lowercase) type variables instead.
Type erasure
Can you also test for a map with specific element types? This would be handy, say for testing whether a given value is a map from type Int to type Int. Let’s try:
scala> def isIntIntMap(x: Any) = x match { case m: Map[Int, Int] => true case _ => false
}
warning: there were unchecked warnings; re-run with -unchecked for details
isIntIntMap: (x: Any)Boolean
The interpreter emitted an “unchecked warning.” You can find out details by starting the interpreter again with the -unchecked command-line option:
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 15.2 |
Chapter 15 · Case Classes and Pattern Matching |
322 |
scala> :quit
$ scala -unchecked
Welcome to Scala version 2.8.1
(Java HotSpot(TM) Client VM, Java 1.5.0_13). Type in expressions to have them evaluated. Type :help for more information.
scala> def isIntIntMap(x: Any) = x match { case m: Map[Int, Int] => true case _ => false
}
<console>:5: warning: non variable type-argument Int in type pattern is unchecked since it is eliminated by erasure
case m: Map[Int, Int] => true
ˆ
Scala uses the erasure model of generics, just like Java does. This means that no information about type arguments is maintained at runtime. Consequently, there is no way to determine at runtime whether a given Map object has been created with two Int arguments, rather than with arguments of different types. All the system can do is determine that a value is a Map of some arbitrary type parameters. You can verify this behavior by applying isIntIntMap to arguments of different instances of class Map:
scala> isIntIntMap(Map(1 -> 1)) res19: Boolean = true
scala> isIntIntMap(Map("abc" -> "abc")) res20: Boolean = true
The first application returns true, which looks correct, but the second application also returns true, which might be a surprise. To alert you to the possibly non-intuitive runtime behavior, the compiler emits unchecked warnings like the one shown above.
The only exception to the erasure rule is arrays, because they are handled specially in Java as well as in Scala. The element type of an array is stored with the array value, so you can pattern match on it. Here’s an example:
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 15.2 |
Chapter 15 · Case Classes and Pattern Matching |
323 |
scala> def isStringArray(x: Any) = x match { case a: Array[String] => "yes"
case _ => "no"
}
isStringArray: (x: Any)java.lang.String
scala> val as = Array("abc")
as: Array[java.lang.String] = Array(abc)
scala> isStringArray(as) res21: java.lang.String = yes
scala> val ai = Array(1, 2, 3) ai: Array[Int] = Array(1, 2, 3)
scala> isStringArray(ai) res22: java.lang.String = no
Variable binding
In addition to the standalone variable patterns, you can also add a variable to any other pattern. You simply write the variable name, an at sign (@), and then the pattern. This gives you a variable-binding pattern. The meaning of such a pattern is to perform the pattern match as normal, and if the pattern succeeds, set the variable to the matched object just as with a simple variable pattern.
As an example, Listing 15.13 shows a pattern match that looks for the absolute value operation being applied twice in a row. Such an expression can be simplified to only take the absolute value one time.
expr match {
case UnOp("abs", e @ UnOp("abs", _)) => e case _ =>
}
Listing 15.13 · A pattern with a variable binding (via the @ sign).
In Listing 15.13, there is a variable-binding pattern with e as the variable and UnOp("abs", _) as the pattern. If the entire pattern match succeeds, then the portion that matched the UnOp("abs", _) part is made available as variable e. As the code is written, e then gets returned as is.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index