- •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 10.15 |
Chapter 10 · Composition and Inheritance |
248 |
10.15Putting it all together
A fun way to exercise almost all elements of the layout library is to write a program that draws a spiral with a given number of edges. This Spiral program, shown in Listing 10.14, will do just that:
import Element.elem
object Spiral {
val space = elem(" ") val corner = elem("+")
def spiral(nEdges: Int, direction: Int): Element = { if (nEdges == 1)
elem("+") else {
val sp = spiral(nEdges - 1, (direction + 3) % 4) def verticalBar = elem('|', 1, sp.height)
def horizontalBar = elem('-', sp.width, 1) if (direction == 0)
(corner beside horizontalBar) above (sp beside space) else if (direction == 1)
(sp above space) beside (corner above verticalBar) else if (direction == 2)
(space beside sp) above (horizontalBar beside corner) else
(verticalBar above corner) beside (space above sp)
}
}
def main(args: Array[String]) { val nSides = args(0).toInt println(spiral(nSides, 0))
}
}
Listing 10.14 · The Spiral application.
Because Spiral is a standalone object with a main method with the proper signature, it is a Scala application. Spiral takes one command-line
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 10.16 |
Chapter 10 · Composition and Inheritance |
249 |
argument, an integer, and draws a spiral with the specified number of edges. For example, you could draw a six-edge spiral as shown below on the left, and larger spirals as shown to the right:
$ scala Spiral 6 |
$ scala Spiral 11 |
$ scala Spiral |
17 |
||
+----- |
+---------- |
|
+---------------- |
|
|
| |
| |
|
| |
|
|
| +-+ |
| +------ |
+ |
| +------------ |
|
+ |
| + | |
| | |
| |
| | |
|
| |
| | |
| | --+ + | |
| | +-------- |
+ | |
||
+---+ |
| | | | | |
| | | |
| | |
||
|
| | ++ | | |
| | | +---- |
+ | | |
||
|
| | |
| | |
| | | | |
| | | |
|
|
| +---- |
+ | |
| | | | ++ | | | |
||
|
| |
| |
| | | | |
| | | |
| |
|
+-------- |
+ |
| | | +-- |
+ | | |
| |
|
|
|
| | | |
| | |
| |
|
|
|
| | +------ |
+ | |
| |
|
|
|
| | |
| |
| |
|
|
|
| +---------- |
+ |
| |
|
|
|
| |
|
| |
|
|
|
+-------------- |
|
+ |
10.16Conclusion
In this section, you saw more concepts related to object-oriented programming in Scala. Among others, you encountered abstract classes, inheritance and subtyping, class hierarchies, parametric fields, and method overriding. You should have developed a feel for constructing a non-trivial class hierarchy in Scala. We’ll work with the layout library again in Chapter 14.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Chapter 11
Scala’s Hierarchy
Now that you’ve seen the details of class inheritance in the previous chapter, it is a good time to take a step back and look at Scala’s class hierarchy as a whole. In Scala, every class inherits from a common superclass named Any. Because every class is a subclass of Any, the methods defined in Any are “universal” methods: they may be invoked on any object. Scala also defines some interesting classes at the bottom of the hierarchy, Null and Nothing, which essentially act as common subclasses. For example, just as Any is a superclass of every other class, Nothing is a subclass of every other class. In this chapter, we’ll give you a tour of Scala’s class hierarchy.
11.1 Scala’s class hierarchy
Figure 11.1 shows an outline of Scala’s class hierarchy. At the top of the hierarchy is class Any, which defines methods that include the following:
final def ==(that: Any): Boolean final def !=(that: Any): Boolean def equals(that: Any): Boolean def ##: Int
def hashCode: Int def toString: String
Because every class inherits from Any, every object in a Scala program can be compared using ==, !=, or equals; hashed using ## or hashCode; and formatted using toString. The equality and inequality methods, == and !=, are declared final in class Any, so they cannot be overridden in subclasses.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 11.1 |
Chapter 11 · Scala’s Hierarchy |
251 |
The == method is essentially the same as equals and != is always the negation of equals.1 So individual classes can tailor what == or != means by overriding the equals method. We’ll show an example later in this chapter.
The root class Any has two subclasses: AnyVal and AnyRef. AnyVal is the parent class of every built-in value class in Scala. There are nine such value classes: Byte, Short, Char, Int, Long, Float, Double, Boolean, and
Unit. The first eight of these correspond to Java’s primitive types, and their values are represented at run time as Java’s primitive values. The instances of these classes are all written as literals in Scala. For example, 42 is an instance of Int, 'x' is an instance of Char, and false an instance of Boolean. You cannot create instances of these classes using new. This is enforced by the “trick” that value classes are all defined to be both abstract and final. So if you were to write:
scala> new Int
you would get:
<console>:5: error: class Int is abstract; cannot be instantiated
new Int
ˆ
The other value class, Unit, corresponds roughly to Java’s void type; it is used as the result type of a method that does not otherwise return an interesting result. Unit has a single instance value, which is written (), as discussed in Section 7.2.
As explained in Chapter 5, the value classes support the usual arithmetic and boolean operators as methods. For instance, Int has methods named + and *, and Boolean has methods named || and &&. Value classes also inherit all methods from class Any. You can test this in the interpreter:
1The only cases where == is does not directly call equals is for Java’s boxed numeric classes such as Integer or Long. In Java, a new Integer(1) does not equal a new Long(1) even though for primitive values 1 == 1L. Since Scala is a more regular language than Java it was necessary correct this discrepancy by special-casing the == method for these classes. Likewise, the ## method provides a Scala version of hashing that is the same as Java’s hashCode, except for boxed numeric types, where it works consistently with ==. For instance new Integer(1) and new Long(1) hash the same with ## even though their Java hashCodes are different.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 11.1
Subtype |
Implicit Conversion |
scala Any
Chapter 11 · Scala’s Hierarchy |
|
252 |
||||
|
java.lang String |
|
classes) ... |
|
|
|
scala AnyRef «java.lang.Object» |
|
|
... (other Java |
Scala classes) ... |
scala Null |
|
|
|
|
|
... (other |
|
|
|
|
|
|
scala List |
|
|
scala ScalaObject |
scala Iterable |
scala Seq |
|
|
scala Nothing |
Class hierarchy of Scala. |
scala Unit |
scala Boolean |
scala Char |
|
|
|
Figure 11.1 · |
scala AnyVal |
|
|
|
scala Short |
scala Byte |
|
scala Double |
scala Float |
scala Long |
|
scala Int |
|
|
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 11.1 |
Chapter 11 · Scala’s Hierarchy |
253 |
scala> 42.toString
res1: java.lang.String = 42
scala> 42.hashCode res2: Int = 42
scala> 42 equals 42 res3: Boolean = true
Note that the value class space is flat; all value classes are subtypes of scala.AnyVal, but they do not subclass each other. Instead there are implicit conversions between different value class types. For example, an instance of class scala.Int is automatically widened (by an implicit conversion) to an instance of class scala.Long when required.
As mentioned in Section 5.9, implicit conversions are also used to add more functionality to value types. For instance, the type Int supports all of the operations below:
scala> 42 max 43 res4: Int = 43
scala> 42 min 43 res5: Int = 42
scala> 1 until 5
res6: Range = Range(1, 2, 3, 4)
scala> 1 to 5
res7: Range.Inclusive = Range(1, 2, 3, 4, 5)
scala> 3.abs res8: Int = 3
scala> (-3).abs res9: Int = 3
Here’s how this works: The methods min, max, until, to, and abs are all defined in a class scala.runtime.RichInt, and there is an implicit conversion from class Int to RichInt. The conversion is applied whenever a method is invoked on an Int that is undefined in Int but defined in RichInt. Similar “booster classes” and implicit conversions exist for the other value classes. Implicit conversions will be discussed in detail in Chapter 21.
The other subclass of the root class Any is class AnyRef. This is the base class of all reference classes in Scala. As mentioned previously, on the
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index