- •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 21.6 |
Chapter 21 · Implicit Conversions and Parameters |
495 |
A style rule for implicit parameters As a style rule, it is best to use a custom named type in the types of implicit parameters. For example, the types of prompt and drink in the previous example was not String, but PreferredPrompt and PreferredDrink, respectively. As a counterexample, consider that the maxListImpParm function could just as well have been written with the following type signature:
def maxListPoorStyle[T](elements: List[T]) (implicit orderer: (T, T) => Boolean): T
To use this version of the function, though, the caller would have to supply an orderer parameter of type (T, T) => Boolean. This is a fairly generic type that includes any function from two Ts to a Boolean. It does not indicate anything at all about what the type is for; it could be an equality test, a lessthan test, a greater-than test, or something else entirely.
The actual code for maxListImpParm, given in Listing 21.3, shows better style. It uses an orderer parameter of type T => Ordered[T]. The word Ordered in this type indicates exactly what the implicit parameter is used for: it is for ordering elements of T. Because this orderer type is more explicit, it becomes no trouble to add implicit conversions for this type in the standard library. To contrast, imagine the chaos that would ensue if you added an implicit of type (T, T) => Boolean in the standard library, and the compiler started sprinkling it around in people’s code. You would end up with code that compiles and runs, but that does fairly arbitrary tests against pairs of items!
Thus the style rule: use at least one role-determining name within the type of an implicit parameter.
21.6 View bounds
The previous example had an opportunity to use an implicit but did not. Note that when you use implicit on a parameter, then not only will the compiler try to supply that parameter with an implicit value, but the compiler will also use that parameter as an available implicit in the body of the method! Thus, both uses of orderer within the body of the method can be left out.
When the compiler examines the code in Listing 21.4, it will see that the types do not match up. For example, x of type T does not have a > method, and so x > maxRest does not work. The compiler will not immediately stop,
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 21.6 |
Chapter 21 · Implicit Conversions and Parameters |
496 |
def maxList[T](elements: List[T])
(implicit orderer: T => Ordered[T]): T =
elements match { |
|
|
case |
List() => |
|
throw new IllegalArgumentException("empty list!") |
||
case |
List(x) => x |
|
case |
x :: rest => |
|
val maxRest = maxList(rest) |
// (orderer) is implicit |
|
if |
(x > maxRest) x |
// orderer(x) is implicit |
else maxRest |
|
|
} |
|
|
Listing 21.4 · A function that uses an implicit parameter internally.
however. It will first look for implicit conversions to repair the code. In this case, it will notice that orderer is available, so it can convert the code to orderer(x) > maxRest. Likewise for the expression maxList(rest), which can be converted to maxList(rest)(orderer). After these two insertions of implicits, the method fully type checks.
Look closely at maxList. There is not a single mention of the orderer parameter in the text of the method. All uses of orderer are implicit. Surprisingly, this coding pattern is actually fairly common. The implicit parameter is used only for conversions, and so it can itself be used implicitly.
Now, because the parameter name is never used explicitly, the name could have been anything. For example, maxList would behave identically if you left its body alone but changed the parameter name:
def maxList[T](elements: List[T])
(implicit converter: T => Ordered[T]): T = // same body...
For that matter, it could just as well be:
def maxList[T](elements: List[T])
(implicit iceCream: T => Ordered[T]): T = // same body...
Because this pattern is common, Scala lets you leave out the name of this parameter and shorten the method header by using a view bound. Using a view
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 21.6 |
Chapter 21 · Implicit Conversions and Parameters |
497 |
def |
maxList[T <% Ordered[T]](elements: List[T]): T = |
||
elements match { |
|
||
|
case |
List() => |
|
|
throw new IllegalArgumentException("empty list!") |
||
|
case |
List(x) => x |
|
|
case |
x :: rest => |
|
|
val maxRest = maxList(rest) |
// (orderer) is implicit |
|
|
if |
(x > maxRest) x |
// orderer(x) is implicit |
|
else maxRest |
|
|
} |
|
|
|
Listing 21.5 · A function with a view bound.
bound, you would write the signature of maxList as shown in Listing 21.5. You can think of “T <% Ordered[T]” as saying, “I can use any T, so long as T can be treated as an Ordered[T].” This is different from saying that T is
an Ordered[T], which is what an upper bound, “T <: Ordered[T]”, would say. For example, even though class Int is not a subtype of Ordered[Int], you could still pass a List[Int] to maxList so long as an implicit conversion from Int to Ordered[Int] is available. Moreover, if type T happens to already be an Ordered[T], you can still pass a List[T] to maxList. The compiler will use an implicit identity function, declared in Predef:
implicit def identity[A](x: A): A = x
In this case, the conversion is a no-op; it simply returns the object it is given.
View bounds and upper bounds
The maxListUpBound function, of Listing 21.2, specifies that T is an Ordered[T] with its upper bound, T <: Ordered[T]. By contrast, the maxList function, of Listing 21.5, specifies that T can be treated as an Ordered[T] with its view bound, T <% Ordered[T]. If you compare the code of maxListUpBound with that of maxList, you’ll find that the only non-cosmetic difference between the two is that the upper bound symbol, <:, is changed to a view bound symbol, <%. But maxList of Listing 21.5 can work with many more types.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 21.7 |
Chapter 21 · Implicit Conversions and Parameters |
498 |
21.7 When multiple conversions apply
It can happen that multiple implicit conversions are in scope that would each work. For the most part, Scala refuses to insert a conversion in such a case. Implicits work well when the conversion left out is completely obvious and thus is pure boilerplate. If multiple conversions apply, then the choice isn’t so obvious after all.
Here’s a simple example. There is a method that takes a sequence, a conversion that turns an integer into a range, and a conversion that turns an integer into an array of digits:
scala> def printLength(seq: Seq[Int]) = println(seq.length) printLength: (seq: Seq[Int])Unit
scala> implicit def intToRange(i: Int) = 1 to i
intToRange: (i: Int)scala.collection.immutable.Range.Inclusive with scala.collection.immutable.Range.ByOne
scala> implicit def intToDigits(i: Int) = i.toString.toList.map(_.toInt)
intToDigits: (i: Int)List[Int]
scala> printLength(12) <console>:21: error: type mismatch;
found : Int(12) required: Seq[Int]
Note that implicit conversions are not applicable because they are ambiguous:
...
The ambiguity here is real. Converting an integer to a sequence of digits is completely different from converting it to a range. In this case, the programmer should specify which one is intended and be explicit.
Up through Scala 2.7, that was the end of the story. Whenever multiple implicit conversions applied, the compiler refused to choose between them. The situation was just as with method overloading. If you try to call foo(null), and there are two different foo overloads that accept null, the compiler will refuse. It will say that the method call’s target is ambiguous.
Scala 2.8 loosens this rule. If one of the available conversions is strictly more specific than the others, then the compiler will choose the more specific one. The idea is that whenever there is a reason to believe a programmer
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 21.7 |
Chapter 21 · Implicit Conversions and Parameters |
499 |
would always choose one of the conversions over the others, don’t require the programmer to write it explicitly. After all, method overloading has the same relaxation. Continuing the previous example, if one of the available foo methods takes a String while the other takes an Any, then choose the String version after all. It’s clearly more specific.
To be more precise, one implicit conversion is more specific than another if one of the following applies:
•The argument type of the former is a subtype of the latter’s.
•Both conversions are methods, and the enclosing class of the former extends the enclosing class of the latter.
The motivation to revisit this issue and revise the rule was to improve interoperation between Java collections, Scala collections, and strings. Here’s a simple example among many:
val cba = "abc".reverse
What is the type inferred for cba? Intuitively, the type should be String. Reversing a string should yield another string, right? However, in Scala 2.7, what happened is that "abc" was converted to a Scala collection. Reversing a Scala collection yields a Scala collection, so the type of cba would be a collection. There’s also an implicit conversion back to a string, but that didn’t patch up every problem. For example, in versions prior to Scala 2.8,
"abc" == "abc".reverse.reverse was false!
In Scala 2.8, the type of cba is String. The old implicit conversion to a Scala collection (now named WrappedString) is retained. However, there is a more specific conversion supplied from String to a new type called StringOps. StringOps has many methods such as reverse, but instead of returning a collection, they return a String. The conversion to StringOps is defined directly in Predef, whereas the conversion to a scala collection is defined in a new class, LowPriorityImplicits, which is extended by Predef. Whenever a choice exists between these two conversions, the compiler chooses the conversion to StringOps, because it’s defined in a subclass of the class where the other conversion is defined.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 21.7 |
Chapter 21 · Implicit Conversions and Parameters |
500 |
object Mocha extends Application {
class PreferredDrink(val preference: String)
implicit val pref = new PreferredDrink("mocha")
def enjoy(name: String)(implicit drink: PreferredDrink) { print("Welcome, "+ name)
print(". Enjoy a ") print(drink.preference) println("!")
}
enjoy("reader")
}
Listing 21.6 · Sample code that uses an implicit parameter.
$ scalac -Xprint:typer mocha.scala
[[syntax trees at end of typer]]// Scala source: mocha.scala package <empty> {
final object Mocha extends java.lang.Object with Application with ScalaObject {
// ...
private[this] val pref: Mocha.PreferredDrink = new Mocha.this.PreferredDrink("mocha");
implicit <stable> <accessor>
def pref: Mocha.PreferredDrink = Mocha.this.pref; def enjoy(name: String)
(implicit drink: Mocha.PreferredDrink): Unit = { scala.this.Predef.print("Welcome, ".+(name)); scala.this.Predef.print(". Enjoy a "); scala.this.Predef.print(drink.preference); scala.this.Predef.println("!")
};
Mocha.this.enjoy("reader")(Mocha.this.pref)
}
}
Listing 21.7 · Sample code after type checking and insertion of implicits. Cover · Overview · Contents · Discuss · Suggest · Glossary · Index