Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Programming_in_Scala,_2nd_edition.pdf
Скачиваний:
25
Добавлен:
24.03.2015
Размер:
22.09 Mб
Скачать

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

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]