Before we dive into Scala’s support for object-oriented and functional programming, let’s finish our discussion of the essential features you’ll use in most of your programs.
An important fundamental concept in Scala is that all operators are actually methods. Consider this most basic of examples:
// code-examples/Rounding/one-plus-two-script.scala 1 + 2
That plus sign between the numbers? It’s a method. First, Scala allows non-alphanumeric method
names. You can call methods +
, -
, _$_
, or whatever you desire. Second, this expression is identical to 1.+(2)
. When a method takes one argument, Scala lets you drop both the period and the parentheses, so the method invocation looks like an operator invocation. This is called “infix” notation, where the operator is between the instance and the argument. We’ll find out more about this shortly.
There's another constraint for droping the period and the parentheses: the receiver of the method have to be mentioned explicitly.
E.g. it's not possible to call a method inside a class in the above way ( like '+ 2' within class Int), without explicitly using 'this' as the receiver of the method (thus only valid if used like 'this + 2'). This is also true for imported methods of objects - you can't write 'println "ho"', but also have to mention the receiver explicitly.
Well, we certainly imply this by saying "between the instance and the argument".
Similarly, a method with no arguments can be invoked without the period. This is called “postfix” notation.
Ruby and Smalltalk programmers should now feel right at home. As users of those languages know, these simple rules have far-reaching benefits when it comes to creating programs that flow naturally and elegantly.
So, what characters can you use in identifiers? Here is a summary of the rules for identifiers, used for method and type names, variables, etc. For the precise details, see [ScalaSpec2009]. Scala allows all the printable ASCII characters, such as letters, digits, the underscore ‘_’, and the dollar sign ‘$’, with the exceptions of the “parenthetical” characters, ‘(’, ‘)’, ‘[’, ‘]’, ‘{’, ‘}’, and the “delimiter” characters ‘`’, ‘’’, ‘'’, ‘"’, ‘.’, ‘;’, and ‘,’. Scala allows the other characters between \u0020-\u007F that are not in the sets above, such as mathematical symbols and “other” symbols. These remaining characters are called operator characters and they include characters such as ‘/’, ‘<’, etc.
This would be great to list in an appendix.
By 'this', I meant all the operator characters.
We "informally" list them in the glossary under "operator characters".
val xyz_++= = 1
assigns the variable xyz_++=
the value 1
, while the expression val xyz++= = 1
won’t compile, because the “identifier” could also be interpreted as xyz ++=
, which looks like an attempt to “append” something to xyz
. Similarly, if you have operator characters after the underscore, you can’t mix them with letters and digits. This restriction prevents ambiguous expressions like this: abc_=123
. Is that an identifier abc_=123
or an assignment of the value 123
to abc_
?
val `this is a valid identifier` = "Hello World!"
. Recall that this syntax is also the way to invoke a method on a Java or .NET class when the method’s name is identical to a Scala reserved word, e.g., java.net.Proxy.‵type‵()
.
val
keyword is present.
reckon you meant 'that begin with' instead of 'being with' - both times
Thanks. will fix.
Once you know that all operators are methods, it’s easier to reason about unfamiliar Scala code. You don’t have to worry about special cases when you see new operators. When working with Actors in the section called “A Taste of Concurrency” in Chapter 1, Zero to Sixty: Introducing Scala, you’ll notice that we use an exclamation point (!
) to send a message to an Actor. Now you know that the !
is just another method, as are the other handy shortcut operators you can use to talk to Actors. Similarly, Scala’s XML library provides the \
and \\
operators to dive into document structures. These are just methods on the scala.xml.NodeSeq
class.
This flexible method naming gives you the power to write libraries that feel like a natural extension of Scala itself. You could write a new math library with numeric types that accept addition, subtraction, and all the usual mathematical operators. You could write a new concurrent messaging layer that behaves just like Actors. The possibilities are constrained only by Scala’s method naming limitations.
Just because you can doesn’t mean you should. When designing your own libraries and APIs in Scala, keep in mind that obscure punctuational operators are hard for programmers to remember. Overuse of these can contribute a “line noise” quality of unreadability to your code. Stick to conventions and err on the side of spelling method names out when a shortcut doesn’t come readily to mind.
To facilitate a variety of readable programming styles, Scala is flexible about the use of parentheses in methods. If a method takes no parameters, you can define it without parentheses. Callers must invoke the method without parentheses. If you add empty parentheses, then callers may optionally add parentheses. For example, the size
method for List
has no parentheses, so you write List(1, 2, 3).size
. If you try List(1, 2, 3).size()
, you’ll get an error. However, the length
method for String
does have parentheses in its definition, so both "hello".length()
and "hello".length
will compile.
List(1,2,3).size does not result in an error. Scala allows you omit () parenthesis. For example, scala> "hello World".toLowerCase.toUpperCase res3: java.lang.String = HELLO WORLD
Your confirming what the text says. List(...).size is fine, but not List(...).size(). The methods on java.lang.String with no arguments are defined with (), as required by Java, but Scala lets you drop those parentheses, as in your example.
I think it might be useful to note that this allow for flexibility of implementation
List(1,2,3).size
allows for size to be implemented as either a method or a member field
def size = 10
val size = 10
the client would not have to change when the underlying implementation is changed. I think this has been stated at a few places as "Scala's uniform access principle"
The convention in the Scala community is to omit parentheses when calling a method that has no side-effects. So, asking for the size of a sequence is fine without parentheses, but defining a method that transforms the elements in the sequence should be written with parentheses. This convention signals a potentially tricky method for users of your code.
It’s also possible to omit the dot (period) when calling a parameterless method or one that takes only one argument. With this in mind, our List(1, 2, 3).size
example above could be written as:
// code-examples/Rounding/no-dot-script.scala List(1, 2, 3) size
Neat, but confusing. When does this syntactical flexibility become useful? When chaining method calls together into expressive, self-explanatory “sentences” of code:
// code-examples/Rounding/no-dot-better-script.scala def isEven(n: Int) = (n % 2) == 0 List(1, 2, 3, 4) filter isEven foreach println
As you might guess, running the above produces the output:
2 4
Scala’s liberal approach to parentheses and dots on methods provides one building block for writing Domain-Specific Languages. We’ll learn more about them after a brief discussion of operator precedence.
So, if an expression like 2.0 * 4.0 / 3.0 * 5.0
is actually a series of method calls on Doubles
, what are the operator precedence rules? Here they are in order from lowest to highest precedence [ScalaSpec2009].
operator precedence'' rules? The following
scala The markup for the text above seems to have gone bad.
Yea, it did. Will fix.
Characters on the same line have the same precedence. An exception is =
when used for assignment, when it has the lowest precedence.
Since * and / have the same precedence, the two lines in the following scala
session behave the same.
scala> 2.0 * 4.0 / 3.0 * 5.0 res2: Double = 13.333333333333332 scala> (((2.0 * 4.0) / 3.0) * 5.0) res3: Double = 13.333333333333332
In a sequence of left-associative method invocations, they simply bind in left-to-right order. “Left-associative” you say? In Scala, any method with a name that ends with a colon ‘:’ actually binds to the right, while all other methods bind to the left. For example, you can prepend an element to a List
using the ::
method (called “cons”, short for “constructor”).
scala> val list = List('b', 'c', 'd') list: List[Char] = List(b, c, d) scala> 'a' :: list res4: List[Char] = List(a, b, c, d)
The second expression is equivalent to list.::(
. In a sequence of right-associative method invocations, they bind from right to left. What about a mixture of left-binding and right-binding expressions?a
)
scala> 'a' :: list ++ List('e', 'f') res5: List[Char] = List(a, b, c, d, e, f)
(The ++
method appends two lists.) In this case, list
is added to the List(
, then e
, f
)
is prepended to create the final list. It’s usually better to add parentheses to remove any potential uncertainty.a
Any method whose name ends with a :
binds to the right, not the left.
When you use the scala
command, either interactively or with scripts, it may appear that you can define “global” variables and methods outside of types. This is actually an illusion; the interpreter wraps all definitions in an anonymous type before generating JVM or .NET CLR byte code.
Domain-Specific Languages, or DSLs, provide a convenient syntactical means for expressing goals in a given problem domain. For example, SQL provides just enough of a programming language to handle the problems of working with databases, making it a domain-specific language.
While some DSLs like SQL are self-contained, it’s become popular to implement DSLs as subsets of full-fledged programming languages. This allows programmers to leverage the entirety of the host language for edge cases that the DSL does not cover, and saves the work of writing lexers, parsers, and the other building blocks of a language.
Scala’s rich, flexible syntax makes writing DSLs a breeze. Consider this example of a style of test writing called Behavior-Driven Development [BDD] using the Specs library (see the section called “Specs”).
// code-examples/Rounding/specs-script.scala "nerd finder" should { "identify nerds from a List" in { val actors = List("Rick Moranis", "James Dean", "Woody Allen") val finder = new NerdFinder(actors) finder.findNerds mustEqual List("Rick Moranis", "Woody Allen") } }
Notice how much this code reads like English: “this should test that in the following scenario”, “this value must equal that value”, and so forth. This example uses the superb Specs library, which effectively provides a DSL for the behavior-driven development testing and engineering methodology. By making maximum use of Scala’s liberal syntax and rich methods, Specs test suites are readable even by non-developers.
Currently specs builds with scala 2.7 version and the jar obtained from the specs website cannot be used with scala 2.8
So, tho this example makes sense, there is no way to try it out at this time
This is just a taste of the power of DSLs in Scala. We’ll see other examples later and learn how to write our own as we get more advanced (see Chapter 11, Domain-Specific Languages in Scala).
Even the most familiar language features are supercharged in Scala, let’s have a look at the lowly if
statement. As in most every language, Scala’s if
evaluates a conditional expression, then proceeds to a block if the result is true
or branches to an alternate block if the result is false
. A simple example:
// code-examples/Rounding/if-script.scala if (2 + 2 == 5) { println("Hello from 1984.") } else if (2 + 2 == 3) { println("Hello from Remedial Math class?") } else { println("Hello from a non-Orwellian future.") }
What’s different in Scala is that if
and almost all other statements are actually expressions themselves. So, we can assign the result of an if
expression, as shown in this example:
// code-examples/Rounding/assigned-if-script.scala val configFile = new java.io.File("~/.myapprc") val configFilePath = if (configFile.exists()) { configFile.getAbsolutePath() } else { configFile.createNewFile() configFile.getAbsolutePath() }
Note that if
statements are expressions, meaning they have values. In this example, the value configFilePath
is the result of an if
expression that handles the case of a configuration file not existing internally, then returns the absolute path to that file. This value can now be reused throughout an application, and the if
expression won’t be re-evaluated when the value is used.
Perhaps point out that this only makes sense in an if / else. Scala allows val x = if true 1; but it returns ().
"return" is not very good choice of word, I think. "Note that if statement is an expression, which evaluates to a value" sounds better to me.
Thanks, both of you, for these comments. Will clarify.
Because if
statements are expressions in Scala, there is no need for the special-case ternary conditional expressions that exists in C-derived languages. You won’t see x ? doThis() : doThat()
in Scala. Scala provides a mechanism that’s just as powerful and more readable.
Huh? How does it clutter up it anymore than if-then-else? Though, I admit the latter being a bit more readable.
Reworded a bit.
What if we omit the else
clause in the previous example? Typing the code in the scala
interpreter will tell us what happens.
scala> val configFile = new java.io.File("~/.myapprc") configFile: java.io.File = ~/.myapprc scala> val configFilePath = if (configFile.exists()) { | configFile.getAbsolutePath() | } configFilePath: Unit = () scala>
Note that configFilePath
is now Unit
. (It was String
before.) The type inference picks a type that works for all outcomes of the if expression. Unit
is the only possibility, since no value is one possible outcome.
This is incorrect (at least in 2.8)
the type of the if expression is inferred to be Any and the result could be either the String or Unit. This seem more correct as Unit is value and not a type
scala> ().isInstanceOf[Any]
res37: Boolean = true
scala> val configFile = new java.io.File("~/.myapprc") configFile: java.io.File = ~/.myapprc
scala> val configFilePath = if (configFile.exists()) {
| configFile.getAbsolutePath()
| }
configFilePath: Any = ()
Another familiar control structure that’s particularly feature-rich in Scala is the for
loop, referred to in the Scala community as a for
comprehension or for
expression. This corner of the language deserves at least one fancy name, because it can do some great party tricks.
Maybe it can be briefly described why for
is referred to as "comprehension" and not "loop".
Will add a brief description
Actually, the term comprehension
comes from functional programming. It expresses the idea that we are traversing a set of some kind, “comprehending” what we find, and computing something new from it.
Let’s start with a basic for
expression:
// code-examples/Rounding/basic-for-script.scala val dogBreeds = List("Doberman", "Yorkshire Terrier", "Dachshund", "Scottish Terrier", "Great Dane", "Portuguese Water Dog") for (breed <- dogBreeds) println(breed)
As you might guess, this code says “for every element in the List dogBreeds
, create a temporary variable called breed
with the value of that element, then print it.” Think of the <-
operator as an arrow directing elements of a collection, one-by-one, to the scoped variable by which we’ll refer to them inside the for
expression. The left-arrow operator is called a generator, so named because it’s generating individual values from a collection for use in an expression.
Maybe you could note, that it's possible to apply more than one Generator within a single for comprehension, like for( friend <- petFriends; breed <- dogBreeds; ... ){ ... }
We have examples later on.
What if we want to get more granular? Scala’s for
expressions allow for filters that let us specify which elements of a collection we want to work with. So to find all Terriers in our list of dog breeds, we could modify the above example to the following:
// code-examples/Rounding/filtered-for-script.scala for (breed <- dogBreeds if breed.contains("Terrier") ) println(breed)
To add more than one filter to a for
expression, separate the filters with semicolons:
// code-examples/Rounding/double-filtered-for-script.scala for (breed <- dogBreeds if breed.contains("Terrier"); if !breed.startsWith("Yorkshire") ) println(breed)
You’ve now found all the Terriers that don’t hail from Yorkshire, and hopefully learned just how useful filters can be in the process.
What if, rather than printing your filtered collection, you needed to hand it off to another part of your program? The yield
keyword is your ticket to generating new collections with for
expressions. In the following example, note that we’re wrapping up the for
expression in curly braces, as we would when defining any block.
for
expressions may be defined with parenthesis or curly braces, but using curly braces means you don’t have to separate your filters with semicolons. Most of the time, you’ll prefer using curly braces when you have more than one filter, assignment, etc.
// code-examples/Rounding/yielding-for-script.scala val filteredBreeds = for { breed <- dogBreeds if breed.contains("Terrier") if !breed.startsWith("Yorkshire") } yield breed
Every time through the for
expression, the filtered result is yielded as a value named breed
. These results accumulate with every run, and the resulting collection is assigned to the value filteredBreeds
(as we did with if
statements above). The type of the collection resulting from a for-yield
expression is inferred from the type of the collection being iterated over. In this case, filteredBreeds
is of type List[String]
, since it is a subset of the dogBreeds
list, which is also of type List[String]
.
Regarding the "Tip" above (parens or braces). Is there a recommendation on what is preferred or common in Scala community?
Will expand the tip briefly. Short answer, people almost always use {}.
One final useful feature of Scala’s for
comprehensions is the ability to define variables inside the first part of your for
expressions that can be used in the latter part. This is best illustrated with an example:
// code-examples/Rounding/scoped-for-script.scala for { breed <- dogBreeds upcasedBreed = breed.toUpperCase() } println(upcasedBreed)
Note that without declaring upcasedBreed
as a val
you can reuse it within the body of your for
expression. This approach is ideal for transforming elements in a collection as you loop through them.
Finally, in the section called “Options and For Comprehensions” in Chapter 13, Application Design, we’ll see how using Options
with for
comprehensions can greatly reduce code size by eliminating unnecessary “null” and “missing” checks.
Familiar in many languages, the while
loop executes a block of code as long as a condition is true. For example, the following code prints out a complaint once a day until the next Friday the 13th has arrived:
// code-examples/Rounding/while-script.scala // WARNING: This script runs for a LOOOONG time! import java.util.Calendar def isFridayThirteen(cal: Calendar): Boolean = { val dayOfWeek = cal.get(Calendar.DAY_OF_WEEK) val dayOfMonth = cal.get(Calendar.DAY_OF_MONTH) // Scala returns the result of the last expression in a method (dayOfWeek == Calendar.FRIDAY) && (dayOfMonth == 13) } while (!isFridayThirteen(Calendar.getInstance())) { println("Today isn't Friday the 13th. Lame.") // sleep for a day Thread.sleep(86400000) }
You can find a table of the conditional operators that work in while
loops below.
You could consider tweaking the above example to be runnable in the REPL.
I pasted this one into the REPL before realizing that I can't really run it as it sleeps for a day as indicated in the comment. I had to kill the REPL.
Good point! We like the example, so we'll add a warning comment!
I'm puzzled; why don't all conditional operators work in while loops? Or do you cover that later as well?
Like the while
loop above, a do-while
loop executes some code while a conditional expression is true. The only difference that a do-while
checks to see if the condition is true after running the block. To count up to ten, we could write this:
// code-examples/Rounding/do-while-script.scala var count = 0 do { count += 1 println(count) } while (count < 10)
As it turns out, there’s a more elegant way to loop through collections in Scala, as we’ll see in the next section.
This example doesn't loop through a collection, it loops over a range. And we saw looping over a collection in an earlier section.
Remember the arrow operator (<-
) from the discussion above about for
loops? We can put it to work here, too. Let’s clean up the do-while
example above:
// code-examples/Rounding/generator-script.scala for (i <- 1 to 10) println(i)
Yup, that’s all that’s necessary. This clean one-liner is possible because of Scala’s RichInt
class. An implicit conversion is invoked by the compiler to convert the 1
, an Int
, into a RichInt
. (We’ll discuss these conversions in the section called “The Scala Type Hierarchy” in Chapter 7, The Scala Object System and in the section called “Implicit Conversions” in Chapter 8, Functional Programming in Scala.) RichInt
defines a to
method that takes another integer and returns an instance of Range.Inclusive
. That is, Inclusive
is a nested class in the Range
companion object (a concept we introduced briefly in Chapter 1, Zero to Sixty: Introducing Scala; see Chapter 6, Advanced Object-Oriented Programming In Scala for details). This subclass of the class Range
inherits a number of methods for working with sequences and iterable data structures, including those necessary to use it in a for
loop.
When mentioning RichInt, should'nt there be also a short explanation on how that Int gets converted into RichInt (that is using Predefs given implicits / implicit type conversion) ?
"That's all that's necessary" is misleading, the i variable is not necessary, you can do just
(1 to 10) foreach println
@Mario. True. Will add something about the conversion. @Javier. You're right, although technically foreach and for loops are different constructs.
RichInt defines a to method that takes another integer and returns an instance of Range.Inclusive. That is, Inclusive is a nested class in the Range companion object
I do not understand how you draw the implication (using That is) that we are using the Range companion object and not the class
By the way, if you wanted to count from 1 up to, but not including 10, you could use until
instead of to
, for example for (i <- 0 until 10)
.
This should paint a clearer picture of how Scala’s internal libraries compose to form easy-to-use language constructs.
When working with loops in most languages, you can break
out of a loop or continue
the iterations. Scala doesn’t have either of these statements, but when writing idiomatic Scala code, they’re not necessary. Use conditional expressions to test if a loop should continue, or make use of recursion. Better yet, filter your collections ahead of time to eliminate complex conditions within your loops.
However, because of demand for it, Scala version 2.8 includes support for break
, implemented as a library method, rather than a built-in break keyword.
The main text should read well on its own if the notes are moved to a sidebar, skipped by the reader, or otherwise taken out of flow. I would either move the comment on 2.8's break method in with the note, or change the note to be part of the main text.
Scala borrows most of the conditional operators from Java and its predecessors. You’ll find the following in if
statements, while
loops, and everywhere else conditions apply.
Table 3.1. Conditional Operators
Operator | Operation | Description |
---|---|---|
| and | The values on the left and right of the operator are true. The right-hand side is only evaluated if the left-hand side is true. |
| or | At least one of the values on the left or right is true. The right-hand side is only evaluated if the left-hand side is false. |
| greater than | The value on the left is greater than the value on the right. |
| greater than or equals | The value on the left is greater than or equal to the value on the right. |
| less than | The value on the left is less than the value on the right. |
| less than or equals | The value on the left is less than or equal to the value on the right. |
| equals | The value on the left is the same as the value on the right. |
| not equal | The value on the left is not the same as the value on the right. |
The greater than or equals operator should be >=, not =>, I guess.
Note that &&
and ||
are “short-circuiting” operators. They stop evaluating expressions as soon as the answer is known.
We’ll discuss object equality in more detail in the section called “Equality of Objects” in Chapter 6, Advanced Object-Oriented Programming In Scala. For example, we’ll see that ==
has a different meaning in Scala vs. Java. Otherwise, these operators should all be familiar, so let’s move on to something new and exciting.
An idea borrowed from functional languages, pattern matching is a powerful yet concise way to make a programmatic choice between multiple conditions. Pattern matching is the familiar case
statement from your favorite C-like language, but on steroids. In the typical case
statement you’re limited to matching against values of ordinal types, yielding trivial expressions like this: "in the case that i
is 5, print a message; in the case that i
is 6, exit the program". With Scala’s pattern matching, your cases can include types, wild-cards, sequences, and even deep inspections of an object’s variables.
It may be good to mention that pattern matching is different from regular expressions. Readers who haven't been exposed to functional programming may not be aware of pattern matching and may think of this as something to do with regex.
We get into this later.
To begin with, let’s simulate flipping a coin by matching the value of a boolean:
// code-examples/Rounding/match-boolean-script.scala val bools = List(true, false) for (bool <- bools) { bool match { case true => println("heads") case false => println("tails") case _ => println("something other than heads or tails (yikes!)") } }
It looks just like a C-style case
statement, right? The only difference is the last case
with the underscore ‘_’ wild card. It matches anything not defined in the cases above it, so it serves the same purpose as the default
keyword in Java and C# switch
statements.
I thinks
val bools = List(true, false)
should be
val bools = List(true, false, 1)
to demonstrate the catch-all case.
In the first case bools is inferred to by List[Boolean] and in the second List[AnyVal]
Pattern matching is eager; the first match wins. So, if you try to put a case _
clause before any other case
clauses, the compiler will throw an “unreachable code” error on the next clause, because nothing will get past the default clause!
Use case _
for the default, “catch-all” match.
What if we want to work with matches as variables?
// code-examples/Rounding/match-variable-script.scala import scala.util.Random val randomInt = new Random().nextInt(10) randomInt match { case 7 => println("lucky seven!") case otherNumber => println("boo, got boring ol' " + otherNumber) }
In this example, we assign the wild card case to a variable called otherNumber
, then print it in the subsequent expression. If we generate a seven, we’ll extoll that number’s virtues. Otherwise, we’ll curse fate for making us suffer an unlucky number.
I think the use of variables in "Matching on Type" example is clearer than the above, because you gain something by using a variable there (like being able to perform string operations). It was confusing to me what the difference between 'otherNumber' and 'randomInt' was.
These simple examples don’t even begin to scratch the surface of Scala’s pattern matching features. Let’s try matching based on type:
// code-examples/Rounding/match-type-script.scala val sundries = List(23, "Hello", 8.5, 'q') for (sundry <- sundries) { sundry match { case i: Int => println("got an Integer: " + i) case s: String => println("got a String: " + s) case f: Double => println("got a Double: " + f) case other => println("got something else: " + other) } }
Here we pull each element out of a List
of Any
type of element, in this case containing a String
, a Double
, an Int
, and a Char
. For the first three of those types, we let the user know specifically which type we got and what the value was. When we get something else (the Char
), we just let the user know the value. We could add further elements to the list of other types and they’d be caught by the other
wild card case.
You might want to add at least one operation unique to one of the types to make it crystal clear that using a variable gives you some benefit. Like change the 's' case to this:
case s: String => println("got a String of length " + s.length)
Then call attention to the fact that sundry.length doesn't compile but s.length does.
Since working in Scala often means working with sequences, wouldn’t it be handy to be able to match against the length and contents of lists and arrays? The following example does just that, testing two lists to see if they contain four elements, the second of which is the integer 3
.
// code-examples/Rounding/match-seq-script.scala val willWork = List(1, 3, 23, 90) val willNotWork = List(4, 18, 52) val empty = List() for (l <- List(willWork, willNotWork, empty)) { l match { case List(_, 3, _, _) => println("Four elements, with the 2nd being '3'.") case List(_*) => println("Any other list with 0 or more elements.") } }
In the second case
of we’ve used a special wild card pattern to match a List
of any size, even zero elements, and any element values. You can use this pattern at the end of any sequence match to remove length as a condition.
This example is a bit confusing. The message in the second case is not exact -- a list of [1, 3] would also print this.
Will fix. Thanks.
Recall that we mentioned the “cons” method for List, ::
. The expression a :: list
prepends a
to a list. You can also use this operator to extract the head and tail of a list.
// code-examples/Rounding/match-list-script.scala val willWork = List(1, 3, 23, 90) val willNotWork = List(4, 18, 52) val empty = List() def processList(l: List[Any]): Unit = l match { case head :: tail => format("%s ", head) processList(tail) case Nil => println("") } for (l <- List(willWork, willNotWork, empty)) { print("List: ") processList(l) }
The processList
method matches on the List
argument l. It may look strange to start the method definition like the following.
def processList(l: List[Any]): Unit = l match { ... }
Hopefully hiding the details with the ellipsis makes the meaning a little clearer. The processList
method is actually one statement that crosses several lines.
It first matches on head :: tail
, where head
will be assigned the first element in the list and tail
will be assigned the rest of the list. That is, we’re extracting the head and tail from the list using ::. When this case matches, it prints the head
and calls processList
recursively to process the tail.
There's nothing after "using" in the sentence: "...extracting the head and tail from the list using."
The second case matches the empty list, Nil
. It prints an end of line and terminates the recursion.
Any reason the second case specifically uses Nil ove
case List()
Alternately, if we just wanted to test that we have a tuple of two items, we could do a tuple match:
// code-examples/Rounding/match-tuple-script.scala val tupA = ("Good", "Morning!") val tupB = ("Guten", "Tag!") for (tup <- List(tupA, tupB)) { tup match { case (thingOne, thingTwo) if thingOne == "Good" => println("A two-tuple starting with 'Good'.") case (thingOne, thingTwo) => println("This has two things: " + thingOne + " and " + thingTwo) } }
In the second case
in this example, we’ve extracted the values inside the tuple to scoped variables, then reused these variables in the resulting expression.
Consider clarifying the below Tip portion "or a meaningfully-named variable" to explain that it must not have a type declaration, the lack of type declaration is what allows it to be bound to anything.
Thanks. will clarify.
In the first case we’ve added a new concept: guards. The if
condition after the tuple is a guard. The guard is evaluated when matching, but only extracting any variables in the preceding part of the case. Guards provide additional granularity when constructing cases. In this example, the only difference between the two patterns is the guard expression, but that’s enough for the compiler to differentiate them.
Recall that the cases in a pattern match are evaluated in order. For example, if your first case is broader than your second case, the second case will never be reached. (Unreachable cases will cause a compiler error.) You may include a “default” case at the end of a pattern match, either using the underscore wild card character or a meaningfully-named variable. When using a variable, it should have no explicit type or it should be declared as Any
, so it can match anything. On the other hand, try to design your code to avoid a catch-all clause by ensuring it only receives specific items that are expected.
Let’s try a deep match, examining the contents of objects in our pattern match.
"It is often helpful to include a catch-all case"
I don't think _ should be encouraged. If one later adds another case class, there's a good change some method won't be updated. Even if not that, it's better to be explicit... it's way more readable (of course, there are exceptions)
Will clarify the wording.
Thought more about it; will add a "note" to encourage thinking more careful about why you might have "none of the above" and make sure it's really the right thing to do.
// code-examples/Rounding/match-deep-script.scala case class Person(name: String, age: Int) val alice = new Person("Alice", 25) val bob = new Person("Bob", 32) val charlie = new Person("Charlie", 32) for (person <- List(alice, bob, charlie)) { person match { case Person("Alice", 25) => println("Hi Alice!") case Person("Bob", 32) => println("Hi Bob!") case Person(name, age) => println("Who are you, " + age + " year-old person named " + name + "?") } }
Poor Charlie gets the cold shoulder, as we can see in the output for the above example:
Hi Alice! Hi Bob! Who are you, 32 year-old person named Charlie?
We first define a case class, a special type of class that we’ll learn more about in the section called “Case Classes” in Chapter 6, Advanced Object-Oriented Programming In Scala. For now, it will suffice to say that a case class allows for very terse construction of simple objects with some pre-defined methods. Our pattern match then looks for Alice and Bob by inspecting the values passed to the constructor of the Person
case class. Charlie falls through to the catch-all case; even though he has the same age
value as Bob, we’re matching on the name
property as well.
I noticed that the use of "default" and "catch-all" is inconsistent, had to go back and read the section, the two words are used to mean the case thing.
However I think one should be use for
case _ (catch-all)
and the other for
case Person(name, age) -> default
Keeps the ducks lined up, right now they are all over the place
This type of pattern match becomes extremely useful when working with Actors, as we’ll see later on. Case classes are frequently sent to Actors as messages, and deep pattern matching on an object’s contents is a convenient way to "parse" those messages.
is a convenient way
Thanks. will fix.
The section on case classes repeatedly mentions "deep pattern matching" but the selected example is not different from the ones in previous section and does not demonstrate any deep matching
case class Address(location:String, city:String, state:String) case class Person(address:Address, name:String, age:Int)
val bhaskar = new Person(new Address("Hookey lane", "Wonderland", "Chocolate Factory"), "Bhaskar", 8726) val dave = new Person(new Address("Mills Road", "Wilmington", "Delaware"), "David", 53553)
for(p <- List(bhaskar, dave)){
p match {
case Person(Address(_, city, "Chocolate Factory"), name, age) => println("Whoo hoo " + name + ", " + age + " still lives in Chocolate Factory, never grew up")
case Person(Address(_, city, state), name, age) if (age > 10000) => println(name + " from city " + city + " and state " + state + " is " + age + " ollllldddd ")
case Person(_,_,_) => println("You are just boring")
}
}
hopefully formats better this time
case class Address(location:String, city:String, state:String) case class Person(address:Address, name:String, age:Int)val bhaskar = new Person(new Address("Hookey lane", "Wonderland", "Chocolate Factory"), "Bhaskar", 8726) val dave = new Person(new Address("Mills Road", "Wilmington", "Delaware"), "David", 53553)
for(p <- List(bhaskar, dave)){ p match { case Person(Address(_, city, "Chocolate Factory"), name, age) => println("Whoo hoo " + name + ", " + age + " still lives in Chocolate Factory, never grew up") case Person(Address(_, city, state), name, age) if (age > 10000) => println(name + " from city " + city + " and state " + state + " is " + age + " ollllldddd ")
case Person(_,_,_) => println("You are just boring") } }
Regular expressions are convenient for extracting data from strings that have an informal structure, but are not “structured data” (that is, in a format like XML or JSON, for example). Commonly referred to as regexes, regular expressions are a feature of nearly all modern programming languages. They provide a terse syntax for specifying complex matches, one which is typically translated into a state machine behind the scenes for optimum performance.
Regexes in Scala should contain no surprises if you’ve used them in other programming languages. Let’s see an example.
// code-examples/Rounding/match-regex-script.scala val BookExtractorRE = """Book: title=([^,]+),\s+authors=(.+)""".r val MagazineExtractorRE = """Magazine: title=([^,]+),\s+issue=(.+)""".r val catalog = List( "Book: title=Programming Scala, authors=Dean Wampler, Alex Payne", "Magazine: title=The New Yorker, issue=January 2009", "Book: title=War and Peace, authors=Leo Tolstoy", "Magazine: title=The Atlantic, issue=February 2009", "BadData: text=Who put this here??" ) for (item <- catalog) { item match { case BookExtractorRE(title, authors) => println("Book \"" + title + "\", written by " + authors) case MagazineExtractorRE(title, issue) => println("Magazine \"" + title + "\", issue " + issue) case entry => println("Unrecognized entry: " + entry) } }
We start with two regular expressions, one for records of books and another for records of magazines. Calling .r
on a string turns it into a regular expression; we use raw (triple-quoted) strings here to avoid having to double-escape backslashes. Should you find the .r
transformation method on strings unclear, you can also define regexes by creating new instances of the Regex
class, as in: new Regex("""\W""")
.
Notice that each of our regexes defines two capture groups, connoted by parentheses. Each group captures the value of a single field in the record, such as a book’s title or author. Regexes in Scala translate those capture groups to extractors. Every match sets a field to the captured result; every miss is set to null
.
What does this mean in practice? If the text fed to the regular expression matches, case BookExtractorRE(title, authors)
will assign the first capture group to title
and the second to authors
. We can then use those values on the right-hand side of the case
clause, as we have in the above example. The variable names title
and author
within the extractor are arbitrary; matches from capture groups are simply assigned from left to right, and you can call them whatever you’d like.
That’s regexes in Scala in nutshell. The scala.util.matching.Regex
class supplies several handy methods for finding and replacing matches in strings, both all occurrences of a match and just the first occurrence, so be sure to make use of them.
What we won’t cover in this section is the details of writing regular expressions. Scala’s Regex
class uses the underlying platform’s regular expression APIs (that is, Java’s or .NET’s). Consult references on those APIs for the hairy details, as they may be subtly different than the regex support in your language of choice.
Sometimes you want to bind a variable to an object enclosed in a match, where you are also specifying match criteria on the nested object. Suppose we modify the previous example so we’re matching on the key-value pairs from a map. We’ll store our same Person
objects as the values and use an employee id as the key. We’ll also add another attribute to Person
, a role
field that points to an instance from a type hierarchy.
// code-examples/Rounding/match-deep-pair-script.scala class Role case object Manager extends Role case object Developer extends Role case class Person(name: String, age: Int, role: Role) val alice = new Person("Alice", 25, Developer) val bob = new Person("Bob", 32, Manager) val charlie = new Person("Charlie", 32, Developer) for (item <- Map(1 -> alice, 2 -> bob, 3 -> charlie)) { item match { case (id, p @ Person(_, _, Manager)) => format("%s is overpaid.\n", p) case (id, p @ Person(_, _, _)) => format("%s is underpaid.\n", p) } }
The case objects
are just singleton objects like we’ve seen before, but with the special case
behavior. We’re most interested in the embedded p @ Person(…)
inside the case clause. We’re matching on particular kinds of Person
objects inside the enclosing tuple. We also want to assign the Person
to a variable p, so we can use it for printing.
You could use "p: Person" instead of "p @ Person(_, _, )" for the second case statement. You might also want to either use id or replace it with "".
format from Predef from Console.format is deprecated, also using 2.8 REPL does not result in any output on the REPL
Since this is the last example with pattern matching, thought I would mention that I do not recall an example the demonstrates using the result of a match expression.
Match expressions return the result of the evaluated case, building on your example I came up with this
val allemps = Map(1->alice, 2->bob, 3->charlie) val pooremps = allemps.filter(_ match {case (id, p @ Person(_, , Manager)) => false case (id, p @ Person(, , )) => true})for(e <- pooremps) println(e)
Person(Alice,25,Developer) is underpaid. Person(Bob,32,Manager) is overpaid. Person(Charlie,32,Developer) is underpaid.
If we weren’t using matching criteria in Person
itself, we could just write p: Person
. For example, the previous match
clause could be written this way.
item match { case (id, p: Person) => p.role match { case Manager => format("%s is overpaid.\n", p) case _ => format("%s is underpaid.\n", p) } }
Note that the p @ Person(…)
syntax gives us a way to flatten this nesting of match statements into one statement. It is analogous to using “capture groups” in a regular expression to pull out substrings we want, instead of splitting the string in several successive steps to extract the substrings we want. Use whichever technique you prefer.
Through its use of functional constructs and strong typing, Scala encourages a coding style that lessens the need for exceptions and exception handling. But where Scala interacts with Java, exceptions are still prevalent.
Scala does not have checked exceptions, like Java. Even Java’s checked exceptions are treated as unchecked by Scala. There is also no throws
clause on method declarations. However, there is a @throws
annotation that is useful for Java interoperability. See the section called “Annotations” in Chapter 13, Application Design.
Thankfully, Scala treats exception handling as just another pattern match, allowing us to make smart choices when presented with a multiplicity of potential exceptions. Let’s see this in action:
// code-examples/Rounding/try-catch-script.scala import java.util.Calendar val then = null val now = Calendar.getInstance() try { now.compareTo(then) } catch { case e: NullPointerException => println("One was null!"); System.exit(-1) case unknown => println("Unknown exception " + unknown); System.exit(-1) } finally { println("It all worked out.") System.exit(0) }
In the above example, we explicitly catch the NullPointerException
thrown when trying to compare a Calendar
instance with null
. We also define unknown
as a catch-all case, just to be safe. If we weren’t hard-coding this program to fail, the finally
block would be reached and the user would be informed that everything worked out just fine.
You can use an underscore (Scala’s standard wild card character) as a placeholder to catch any type of exception (really, to match any case in a pattern matching expression). However, you won’t be able to refer to the exception in the subsequent expression. Name the exception variable if you need it, for example, if you need to print the exception as we do in the catch-all case of the previous example. e
or ex
are fine names.
Pattern matching aside, Scala’s treatment of exception handling should be familiar to those fluent in Java, Ruby, Python, and most other mainstream languages. And yes, you throw an exception by writing throw new MyBadException(…)
. That’s all there is to it.
I don't see a clear, explicit mention that exceptions are not checked, which I think is important to know for people coming from Java land.
Good point. Will add a statement to that effect.
Pattern matching is a powerful and elegant way of extracting information from objects, when used appropriately. Recall from Chapter 1, Zero to Sixty: Introducing Scala that we highlighted the synergy between pattern matching and polymorphism. Most of the time, you want to avoid the problems of “switch” statements that know a class hierarchy, because they have to be modified every time the hierarchy is changed.
In our drawing actor example, we used pattern matching to separate different “categories” of messages, but we used polymorphism to draw the shapes sent to it. We could change the Shape
hierarchy and the actor code would not require changes.
Pattern matching is also useful for the design problem where you need to get at data inside an object, but only in special circumstances. One of the unintended consequences of the JavaBeans [JavaBeansSpec] specification was it encouraged people to expose fields in their objects through getters and setters. This should never be a default decision. Access to “state information” should be encapsulated and exposed only in ways that make logical sense for the type, as viewed from the abstraction it exposes.
Instead, consider using pattern matching for those “rare” times when you need to extract information in a controlled way. An unapply
method can hide the implementation details of the objects it is decomposing. In fact, the information returned by unapply
might be a transformation of the actual information in the type.
ehh unapply?? first reference to the method. I had to go look up the scala documentation on extractor objects
http://www.scala-lang.org/node/112
to understand that line.
Finally, when designing pattern matching statements, be wary of relying on a default case clause. Under what circumstances would “none of the above” be the correct answer? It may indicate that the design should be refined so you know more precisely all the possible matches that might occur. We’ll learn one technique that helps when we discuss sealed class hierarchies in the section called “Sealed Class Hierarchies” in Chapter 7, The Scala Object System.
Remember our examples above involving various breeds of dog? In thinking about the types in these programs, we might want a top-level Breed
type that keeps track of a number of breeds. Such a type is called an enumerated type, and the values it contains are called enumerations.
While enumerations are a built-in part of many programming languages, Scala takes a different route and implements them as a class in its standard library. This means there is no special syntax for enumerations in Scala, as in Java and C#. Instead, you just define an object that extends the Enumeration
class. Hence, at the byte code level, there is no connection between Scala enumerations and the enum
constructs in Java and C#.
Here is in example:
// code-examples/Rounding/enumeration-script.scala object Breed extends Enumeration { val doberman = Value("Doberman Pinscher") val yorkie = Value("Yorkshire Terrier") val scottie = Value("Scottish Terrier") val dane = Value("Great Dane") val portie = Value("Portuguese Water Dog") } // print a list of breeds and their IDs println("ID\tBreed") for (breed <- Breed) println(breed.id + "\t" + breed) // print a list of Terrier breeds println("\nJust Terriers:") Breed.filter(_.toString.endsWith("Terrier")).foreach(println)
When run, you’ll get the following output:
ID Breed 0 Doberman Pinscher 1 Yorkshire Terrier 2 Scottish Terrier 3 Great Dane 4 Portuguese Water Dog Just Terriers: Yorkshire Terrier Scottish Terrier
We can see that our Breed
enumerated type contains several variables of type Value
, as in the following example.
val doberman = Value("Doberman Pinscher")
Each declaration is actually calling a method named Value
that takes a string argument. We use this method to assign a long-form breed name to each enumeration value, which is what the Value.toString
method returned in the output above.
Note that there is no name space collision between the type and method that both have the name Value
. There are other overloaded versions of the Value
method. One of them takes no arguments, another one takes an Int
id value, and another one takes both an Int
and String
. These Value
methods return a Value
object and they add the value to the enumeration’s collection of values.
In fact, Scala’s Enumeration
class supports the usual methods for working with collections, so we can easily iterate through the breeds with a for
loop and filter
them by name. The output above also demonstrated that every Value
in an enumeration is automatically assigned a numeric identifier, unless you call one of the Value
methods where you specify your own id value explicitly.
You’ll often want to give your enumeration values human readable names, as we did here. However, sometimes you may not need them. Here’s another enumeration example adapted from the scaladoc entry for Enumeration
.
// code-examples/Rounding/days-enumeration-script.scala object WeekDay extends Enumeration { type WeekDay = Value val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value } import WeekDay._ def isWorkingDay(d: WeekDay) = ! (d == Sat || d == Sun) WeekDay filter isWorkingDay foreach println
Running this script with scala
yields the following output.
thought I mention this, others might run into the same question, on
val Mon, Tue, Wed, Thu, Fri, Sat, Sun = Value
in Java Object i, j = new Object()
is only an initialization of j, i remain uninitialized. It makes sense that all the enumerations are initialized in scala given the preference for val.
Main$$anon$1$WeekDay(0) Main$$anon$1$WeekDay(1) Main$$anon$1$WeekDay(2) Main$$anon$1$WeekDay(3) Main$$anon$1$WeekDay(4)
When a name isn’t assigned using one of the Value
methods that takes a String
argument, Value.toString
prints the name of the type that is synthesized by the compiler, along with the id value that was generated automatically.
Note that we imported WeekDay._
. This made each enumeration value, e.g., Mon
, Tues
, etc. in scope. Otherwise, you would have to write WeekDay.Mon
, WeekDay.Tues
, etc.
Also, the import made the type alias, type Weekday = Value
in scope, which we used as the type for the argument for the isWorkingDay
method. If you don’t define a type alias like this, then you would declare the method as def isWorkingDay(d: WeekDay.Value)
.
Since Scala enumerations are just regular objects, you could use any object with vals
to indicate different “enumeration values”. However, extending Enumeration
has several advantages. It automatically manages the values as a collection that you can iterate over, etc., as in our examples. It also automatically assigns unique integer ids to each value.
Case classes (see the section called “Case Classes” in Chapter 6, Advanced Object-Oriented Programming In Scala) are often used instead of enumerations in Scala, because the “use case” for them often involves pattern matching. We’ll revisit this topic in the section called “Enumerations vs. Pattern Matching” in Chapter 13, Application Design.
Revisit in where? (the chapter number is missing)
Btw. you probably should mention that instead of Enumeration, often case classes are used.
Fixed the cross reference. Will add note about enums vs. case classes.
We’ve covered a lot of ground in this chapter. We learned how flexible Scala’s syntax can be, and how it facilitates the creation of Domain-Specific Languages. Then we explored Scala’s enhancements to looping constructs and conditional expressions. We experimented with different uses for pattern matching, a powerful improvement on the familiar case-switch
statement. Finally, we learned how to encapsulate values in enumerations.
You should now be prepared to read a fair bit of Scala code, but there’s plenty more about the language to put in your tool belt. In the next four chapters, we’ll explore Scala’s approach to object-oriented programming, starting with traits.
No comments yet
Add a comment