Programming in Scala
A comprehensive step-by-step guide
by Martin Odersky, Lex Spoon & Bill Venners
- On Amazon
- ISBN: 978-0981531649
My notes
A Scalable Language
The name Scala stands for "scalable language". The language is so named because it was designed to grow with the demands of its users.
It runs on the standard Java platform and interoperates seamlessly with all Java libraries.
Scala is a blend of object-oriented and functional programming concepts in a statically typed language.
Scala allows users to grow and adapt the language in the directions they need by defining easy-to-use libraries that feel like native language support.
Scala is an object-oriented language in pure form: every value is an object and every operation is a method call.
In addition to being a pure object-oriented language, Scala is also a fullblown functional language.
Functional programming is guided by two main ideas. The first idea is that functions are first-class values: you can pass functions as arguments to other functions, return them as results from functions, or store them in variables. You can also define a function inside another function. And you can define functions without giving them a name. The second main idea of functional programming is that the operations of a program should map input values to output values rather than change data in place. Immutable data structures are one of the cornerstones of functional programming.
Another way of stating the second idea of functional programming is that methods should not have any side effects. They should communicate with their environment only by taking arguments and returning results. Such methods are called referentially transparent, which means that for any given input the method call could be replaced by its result without affecting the program's semantics.
Functional languages encourage immutable data structures and referentially transparent methods.
Scala programs compile to JVM bytecodes. Scala code can call Java methods, access Java fields, inherit from Java classes, and implement Java interfaces. Scala code can also be invoked from Java code.
Fewer lines of code mean not only less typing, but also less effort at reading and understanding programs and fewer possibilities of defects.
Semicolons are optional in Scala and are usually left out.
Probably the most important key to compact code is code you don't have to write because it is done in a library for you.
Although a static type system certainly cannot replace unit testing, it can reduce the number of unit tests needed by taking care of some properties that would otherwise need to be tested.
Scala has a very sophisticated type inference system that lets you omit almost all type information that's usually considered annoying.
First Steps in Scala
The interactive shell for Scala is simply called scala
.
Scala has two kinds of variables, val
s and var
s. A val
is similar to a final variable in Java. Once initialized, a val
can never be reassigned. A var
is similar to a non-final variable in Java. A var
can be reassigned throughout its lifetime.
Type inference is Scala's ability to figure out types you leave off.
When the Scala compiler can infer types, it is often best to let it do so rather than fill the code with unnecessary, explicit type annotations.
In Scala you specify a variable's type after its name, separated by a colon, for example: val msg: String = "example"
An example for a function definition:
def max(x: Int, y: Int): Int = {
if (x > y)
x
else
y
}
Function definitions start with def
. The function's name is followed by a comma-separated list of parameters in parentheses. A type annotation must follow every function parameter, preceded by a colon, because the Scala compiler does not infer function parameter types. After the close parenthesis of the parameter list there is another type annotation. It defines the result type of the function itself. Following the function's result type is an equals sign and a pair of curly braces that contain the body of the function. The equals sign that precedes the body of a function hints that in the functional world view, a function defines an expression that results in a value.
A result type of Unit
indicates the function returns no interesting value (similar to Java's void
type). Methods with such a result type, therefore, are only executed for their side effects.
Array elements are accessed by specifying an index in parentheses: myArray(0)
, and not myArray[0]
!
The syntax for a function literal is a list of named parameters, in parentheses, a right arrow, and then the body of the function. Example: (x: Int, y: Int) => x + y
Next Steps in Scala
When you define a variable with val
, the variable can't be reassigned, but the object to which it refers could potentially still be changed.
If a method takes only one parameter, you can call it without a dot or parentheses.
When you apply parentheses surrounding one or more values to a variable, Scala will transform the code into an invocation of a method named apply
on that variable. Example: greetStrings(i)
gets transformed to greetStrings.apply(i)
.
When an assignment is made to a variable to which parentheses and one or more arguments have been applied, the compiler will transform that into an invocation of an update
method that takes the arguments in parentheses as well as the object to the right of the equals sign. Example: greetStrings(0) = "Hello"
gets transformed to greetStrings.update(0, "Hello")
.
Scala achieves a conceptual simplicity by treating everything, from arrays to expressions, as objects with methods.
A Scala array is a mutable sequence of objects that all share the same type. For an immutable sequence of objects that share the same type you can use Scala's List
class.
When you call a method on a list that might seem by its name to imply the list will mutate, it instead creates and returns a new list with the new value.
List concatenation is done using the :::
method.
Perhaps the most common operator for lists is ::
, pronounced "cons". It prepends a new element to the beginning of an existing list, and returns the resulting list.
If a method is used in operator notation, such as a * b
, the method is invoked on the left operand, as in a.*(b)
– unless the method name ends in a colon. In this case, the method is invoked on the right operand. Therefore, in 1 :: twoThree
, the ::
method is invoked on twoThree
, passing in 1
, like this: twoThree.::(1)
.
Like lists, tuples are immutable, but unlike lists, tuples can contain different types of elements. To instantiate a new tuple that holds some objects, just place the objects in parentheses, separated by commas (Example: val pair = (99, "Luftballons")
). Once a tuple is instantiated, you can access its elements with a dot, underscore, and the one-based index of the element (Example: pair._1 // returns 99
).
One way to move towards a functional style is to try to program without var
s.
Prefer val
s, immutable objects, and methods without side effects. Reach for them first. Use var
s, mutable objects, and methods with side effects when you have a specific need and justification for them.
Classes and Objects
The way you make members public in Scala is by not explicitly specifying any access modifier. Put another way, where you'd say "public" in Java, you simply say nothing in Scala. Public is Scala's default access level.
Method parameters in Scala are val
s, not var
s. If you attempt to reassign a parameter inside a method, it won't compile.
In the absence of any explicit return statement, a Scala method returns the last value computed by the method. The recommended style for methods is in fact to avoid having explicit, and especially multiple, return statements. Instead, think of each method as an expression that yields one value, which is returned.
Classes in Scala cannot have static members. Instead, Scala has singleton objects. A singleton object definition looks like a class definition, except instead of the keyword class
you use the keyword object
.
When a singleton object shares the same name with a class, it is called that class's companion object. The class and its companion object must be defined in the same source file. The class is called the companion class of the singleton object. A class and its companion object can access each other's private members.
A singleton object that does not share the same name with a companion class is called a standalone object.
To run a Scala program, you must supply the name of a standalone singleton object with a main
method that takes one parameter, an Array[String]
, and has a result type of Unit
. Any standalone object with a main
method of the proper signature can be used as the entry point into an application.
Scala implicitly imports members of packages java.lang
and scala
, as well as the members of a singleton object named Predef
, into every Scala source file.
You can name .scala
files anything you want, no matter what Scala classes or code you put in them. However, it is recommended style to name files after the classes they contain.
Basic Types and Operations
In Scala operators are not special language syntax: any method can be an operator. What makes a method an operator is how you use it. When you write s.indexOf('o')
, indexOf
is not an operator. But when you write s indexOf 'o'
, indexOf
is an operator, because you're using it in operator notation.
The only identifiers that can be used as prefix operators are +, -, !, and ~. The methods invoked by those operators must have unary_
prepended to the operator character.
In Scala, you can leave off empty parentheses on method calls. The convention is that you include parentheses if the method has side effects, but you can leave them off if the method has no side effects.
Functional Types
In Java, classes have constructors, which can take parameters, whereas in Scala, classes can take parameters directly: class Rational(n: Int, d: Int)
.
The Scala compiler will compile any code you place in the class body, which isn't part of a field or a method definition, into the primary constructor.
A precondition is a constraint on values passed into a method or constructor, a requirement which callers must fulfill. One way to do that is to use require
, which takes one boolean parameter and throws an IllegalArgumentException
if the passed value is false:
class Rational(n: Int, d: Int) {
require(d != 0)
}
Constructors other than the primary constructor are called auxiliary constructors. Such constructors start with def this(...)
. Every auxiliary constructor must invoke another constructor of the same class as its first action. The invoked constructor is either the primary constructor, or another auxiliary constructor that comes textually before the calling constructor. The net effect of this rule is that every constructor invocation in Scala will end up eventually calling the primary constructor of the class. The primary constructor is thus he single point of entry of a class.
The convention for constants is that the first character should be upper case.
Built-in Control Structures
Look for opportunities to use val
s. They can make your code both easier to read and easier to refactor.
If there isn't a good justification for a particular while
or do-while
loop, try to find a way to do the same thing without it.
Sometimes you do not want to iterate through a collection in its entirety. You want to filter it down to some subset. You can do this with a for
expression by a adding a filter: an if
clause inside the for
's parentheses. Example:
val filesHere = (new java.io.File(".")).listFiles
for (file <- filesHere if file.getName.endsWith(".scala"))
println(file)
Scala's match
expression lets you select from a number of alternatives, just like switch
statements in other languages. The default case is specified with an underscore. An example:
val firstArg = if (!args.isEmpty) args(0) else ""
val friend =
firstArg match {
case "salt" => "pepper"
case "eggs" => "bacon"
case _ => "huh?"
}
println(friend)
There are a few important differences from Java's switch
statement. One is that any kind of constant can be used in cases in Scala, not just integer-type and enum constants. Another difference is that there are no break
s at the end of each alternative. Instead, the break
is implicit, and there is no fall through from one alternative to the next. The most significant difference may be that match
expressions results in a value.
Functions and Closures
An important design principle of the functional programming style: programs should be decomposed into many small functions that each do a well-defined task. Individual functions are often quite small. The advantage of this style is that it gives a programmer many building blocks that can be composed to do more difficult things. Each building block should be simple enough to be understood individually.
An alternative to private methods: you can define functions inside other functions. Just like local variables, such local functions are visible only in their enclosing block.
Scala has first-class functions. Not only can you define functions and call them, but you can write down functions as unnamed literals and then pass them around as values. Simple example of a function literal: (x: Int) => x + 1
.
If you want to have more than one statement in a function literal, surround its body by curly braces and put one statement per line. Just like a method, when the function value is invoked, all of the statements will be executed, and the value returned from the function is whatever the expression on the last line generates.
To make a function literal more concise, you can use underscores as placeholders for one or more parameters, so long as each parameter appears only one time within the function literal. Multiple underscores mean multiple parameters, not reuse of a single parameter repeatedly. The first underscore represents the first parameter, the second underscore the second parameter, and so on.
A partially applied function is an expression in which you don't supply all of the arguments needed by the function. Instead, you supply some, or none, of the needed arguments.
Scala allows you to indicate that the last parameter to a function may be repeated. This allows clients to pass variable length argument lists to the function. To denote a repeated parameter, place an asterisk after the type of the parameter. Inside the function, the type of the repeated parameter is an Array
of the declared type of the parameter.
Named arguments allow you to pass arguments to a function in a different order. The syntax is simply that each argument is preceded by a parameter name and an equals sign.
Default parameter values are defined like: def printTime(out: java.io.PrintStream = Console.out)
Functions, which call themselves as their last action, are called tail recursive.
Control Abstraction
A curried function is applied to multiple argument lists, instead of just one. Example of a curried function: def curriedSum(x: Int)(y: Int) = x + y)
and its invocation is curriedSum(1)(2)
Composition and Inheritance
Composing operators are often called combinators because they combine elements of some domain into new elements. Thinking in terms of combinators is generally a good way to approach library design: it pays to think about the fundamental ways to construct objects in an application domain. What are the simple objects? In what ways can more interesting objects be constructed out of simpler ones? How do combinators hang together? What are the most general combinations?
A class with abstract members must itself be declared abstract, which is done by writing an abstract
modifier in front of the class
keyword. A method is abstract if it does not have an implementation, there is no abstract
modifier necessary on method declarations. Example:
abstract class Element {
def contents: Array[String] // method without implementation
}
Uniform access principle: client code should not be affected by a decision to implement an attribute as a field or method.
It is encouraged style in Scala to define methods that take no parameters and have no side effects as parameterless methods, i.e., leaving off the empty parentheses. On the other hand, you should never define a method that has side-effects without parentheses, because then invocations of that method would look like a field selection. So your clients might be surprised to see the side effects. Similarly, whenever you invoke a function that has side effects, be sure to include the empty parentheses when you write the invocation.
If you leave out an extends
clause, the Scala compiler implicitly assumes your class extends from scala.AnyRef
, which on the Java platform is the same as class java.lang.Object
.
In Scala, fields and methods belong to the same namespace. This makes it possible for a field to override a parameterless method. On the other hand, it is forbidden to define a field and method with the same name in the same class.
A parametric field is a shorthand that defines at the same time a parameter and a field with the same name. The field is initialized with the value of the parameter. Example: class ArrayElement(val contents: Array[String]) extends Element
. This is the same as class ArrayElement(conts: Array[String]) extends Element { val contents: Array[String] = conts }
.
To invoke a superclass constructor, you simply place the argument or arguments you want to pass in parentheses following the name of the superclass. Example: class LineElement(s: String) extends ArrayElement(Array(s)) { .. }
Scala requires an override
modifier for all members that override a concrete member in a parent class. The modifier is optional if a member implements an abstract member with the same name.
Scala's Hierarchy
Every class inherits from a common superclass named Any
.
The root class Any
has two subclasses: AnyVal
and AnyRef
. AnyVal
is the parent class of every built-in value class in Scala. AnyRef
is the base class of all reference classes.
At the bottom of the type hierarchy are the two classes scala.Null
and scala.Nothing
. Class Null
is the type of the null reference; it is a subclass of every reference class. Type Nothing
is at the very bottom of Scala's class hierarchy; it is a subtype of every other type. One use of Nothing
is that it signals abnormal termination.
Traits
Traits are a fundamental unit of code reuse in Scala. A trait encapsulates method and field definitions, which can then be reused by mixing them into classes. Unlike class inheritance, in which each class must inherit from just one superclass, a class can mix in any number of traits.
A trait definition looks just like a class definition except that it uses the keyword trait
. Once a trait is defined, it can be mixed in to a class using either the extends
or with
keywords.
If you wish to mix a trait into a class that explicitly extends a superclass, you use extends
to indicate the superclass and with
to mix in the trait. If you want to mix in multiple traits, you add more with
clauses.
The trait Ordered
allows you to enrich a class with comparison methods (lt;
, gt;
, lt;=
, and gt;=
) by implementing only one method compare
.
Any time you implement a class that is ordered by some comparison, you should consider mixing in the Ordered
trait.
Traits let you modify the methods of a class, and they do so in a way that allows you to stack those modifications with each other.
If a trait declares a superclass, it means that the trait can only be mixed into a class that also extends the superclass.
The order of mixins is significant. When you call a method on a class with mixins, the method in the trait furthest to the right is called first. If that method calls super
, it invokes the method in the next trait to its left, and so on.
Packages and Imports
You can place code into named packages in Scala in two ways. First, you can place the contents of an entire file into a package by putting a package
clause at the top of the file. The other way is to follow a package
clause by a section in curly braces that contains the definitions that go into the package. This syntax is called a packaging.
Imports in Scala can appear anywhere, not just at the beginning of a compilation unit.
Imports in Scala can also rename or hide members. This is done with an import selector clause enclosed in braces, which follows the object from which members are imported. Examples: import Fruits.{Apple, Orange}
imports only the members Apple
and Orange
from the object Fruits
. import java.sql.{Date => SDate}
imports the SQL date class as SDate
. import Fruits.{Pear => _, _}
imports all members of Fruits
except Pear
.
Access modifiers in Scala can be augmented with qualifiers. A modifier of the form private[X]
or protected[X]
means that access is private or protected "up to" X, where X designates some enclosing package, class or singleton object.
Scala's access rules privilege companion objects and classes when it comes to private or protected accesses. A class shares all its access rights with its companion object and vice versa. In particular, an object can access all private members of its companion class, just as a class can access all private members of its companion object.
Any kind of definition that you can put inside a class, you can also put at the top level of a package. If you have some helper method you'd like to be in scope for an entire package, go ahead and put it right at the top level of the package. To do so, put the definitions in a package object. Each package is allowed to have one package object. Any definitions placed in a package object are considered members of the package itself. Example:
package object bobsdelights {
def showFruit(fruit: Fruit) {
...
}
}
Package objects are compiled to class files named package.class
that are located in the directory of the package that they augment. It's useful to keep the same convention for source files. So you would typically put the source file of the package object bobsdelights
into a file named package.scala
that resides in the bobsdelights
directory.
Assertions and Unit Testing
Assertions in Scala are written as calls of a predefined method assert
.
Assertions can be enabled and disabled using the JVM's -ea
and -da
command-line flags.
You have many options for unit testing in Scala, from established Java tools, such as JUnit and TestNG, to new tools written in Scala, such as ScalaTest, specs, and ScalaCheck.
Case Classes and Pattern Matching
Classes with a case
modifier are called case classes. Using the modifier makes the Scala compiler add some syntactic conveniences to your class. First, it adds a factory method with the name of the class. The second syntactic convenience is that all arguments in the parameter list of a case class implicitly get a val
prefix, so they are maintained as fields. Third, the compiler adds "natural" implementations of the methods toString
, hashCode
, and equals
to your class. Finally, the compiler adds a copy
method to your class for making modified copies. This method is useful for making a new instance of the class that is the same as another one except that one or two attributes are different.
The biggest advantage of case classes is that they support pattern matching.
A pattern match includes a sequence of alternatives, each starting with the keyword case
. Each alternative includes a pattern and one or more expressions, which will be evaluated if the pattern matches. An arrow symbol =>
separates the pattern from the expressions.
A match
expression is evaluated by trying each of the patterns in the order they are written. The first pattern that matches is selected, and the part following the arrow is selected and executed.
If none of the patterns match, an exception named MatchError
is thrown. This means you always have to make sure that all cases are covered, even if it means adding a default case where there's nothing to do.
The wildcard pattern (_
) matches any object whatsoever and is often used as a catch-all alternative: case _ => println("catch-all")
. Wildcards can also be used to ignore parts of an object that you do not care about: case BinOp(_, _, _) => println("binary operation")
A constant pattern matches only itself. Any literal may be used as a constant. Also any val
or singleton object can be used as a constant. Example: case "hello" => "hi!"
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. Example: case somethingElse => "it is: " + somethingElse
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.
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: case List(0, _, _) => println("found it")
. 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: case List(0, _*) => println("found it")
.
Tuple patterns: You can also match against tuples: case (a, b, c) => println("matched arbitrary 3-tuple")
.
You can use a typed pattern as a convenient replacement for type tests and type casts: case s: String => s.length
.
In addition to the standalone variable patterns, you can also add a variable to any other pattern. You simply write the variable name, an @-sign, and then the pattern.
A pattern guard comes after a pattern and starts with an if
. The guard can be an arbitrary boolean expression, which typically refers to variables in the pattern. If a pattern guard is present, the match succeeds only if the guard evaluates to true
. Example: case s: String if s(0) == 'a' => println("starts with an 'a'")
A sealed class cannot have any new subclasses added except the ones in the same file. This is very useful for pattern matching, because it means you only need to worry about the subclasses you already know about. You also get better compiler support. If you match against case classes that inherit from a sealed class, the compiler will flag missing combinations of patterns with a warning message. Therefore, if you write a hierarchy of classes intended to be pattern matched, you should consider sealing them. Simply put the sealed
keyword in front of the class at the top of the hierarchy.
Scala has a standard type named Option
for optional values. Such a value can be of two forms. It can be of the form Some(x)
where x
is the actual value. Or it can be the None
object, which represents a missing value.
Any time you define a val
or a var
, you can use a pattern instead of a simple identifier. Example:
val myTuple = (123, "abc")
val (number, string) = myTuple
A sequence of cases in curly braces can be used anywhere a function literal can be used. Example:
val withDefault: Option[Int] => Int = {
case Some(x) => x
case None => 0
}
Working with Lists
List are immutable. That is, elements of a list cannot be changed by assignment.
Like arrays, lists are homogeneous: the elements of a list all have the same type.
All lists are built from two fundamental building blocks, Nil
and ::
(pronounced "cons"). Nil
represents the empty list. The infix operator ::
expresses list extension at the front.
All operations on lists can be expressed in terms of the following three: head
returns the first element of a list, tail
returns a list consisting of all elements except the first, and isEmpty
returns true
if the list is empty. head
and tail
are defined only for non-empty lists.
A method is first-order if it does not take any functions as arguments.
An operation similar to ::
is list concatenation, written :::
. Example: List(1, 2) ::: List(3, 4) // returns List(1, 2, 3, 4)
It's a good idea to organize your data so that most accesses are the head of a list, rather than the last element.
A common kind of operation combines the elements of a list with some operator, also called folding lists. A fold left operation (z /: xs) (op)
involves three objects: a start value z
, a list xs
, and a binary operation op
. The result of the fold is op
applied between successive elements of the list prefixed by z
. Example: def sum(xs: List[Int]): Int = (0 /: xs) (_ + _)
The :\
is pronounced fold right. It involves the same three operands as fold left, but the first two appear in reversed order: The first operand is the list to fold, the second is the start value.
If you prefer, you can alternatively use the methods named foldLeft
and foldRight
.
Collections
A ListBuffer
is a mutable object, which can help you build lists more efficiently when you need to append. ListBuffer
provides constant time append and prepend operations. You append elements with the +=
operator, and prepend them with the +=:
operator.
An ArrayBuffer
is like an array, except that you can additionally add and remove elements from the beginning and end of the sequence. When you create an ArrayBuffer
, you must specify a type parameter, but need not specify a length. The ArrayBuffer
will adjust the allocated space automatically as needed: val buf = new ArrayBuffer[Int]()
.
By default when you write Set
or Map
you get an Immutable object. If you want the mutable variant, you need to do an explicit import. Scala gives you easier access to the immutable variants, as a gentle encouragement to prefer them over their mutable counterparts.
The key characteristic of sets is that they will ensure that at most one of each object, as determined by ==
, will be contained in the set at any one time.
When you create map, you must specify two types. The first type is for the keys of the map, the second for the values: val map = mutable.Map.empty[String, Int]
.
On occasion you may need a set or map whose iterator returns elements in a particular order. For this purpose, the Scala collections library provides the traits SortedSet
and SortedMap
, which are implemented by the classes TreeSet
and TreeMap
.
The most common way to create and initialize a collection is to pass the initial elements to a factory method on the companion object of your chosen collection. You just place the elements in parentheses after the companion object name, and the Scala compiler will transform that to an invocation of an apply
method on that companion object.
Stateful Objects
In Scala, every var
that is a non-private member of some object implicitly defines a getter and setter method with it. These getters and setters are named differently from the Java convention, however. The getter of a var x
is just named x
, while its setter is named x_=
. The getter and setter get the same visibility as the original var
.
An initializer = _
of a field assigns a zero value to that field. The zero value depends on the field's type. It is 0
for numeric types, false
for booleans, and null
for reference types. Example: var celsius: Float = _
. Note that you cannot simply leave off the = _
initializer in Scala. If you had written var celsius: Float
it would declare an abstract variable, not an uninitialized one.
Type Parameterization
Type parameterization allows you to write generic classes and traits. For example, sets are generic and take a type parameter: they are defined as Set[T]
. As a result, any particular set instance might be a Set[String]
, a Set[Int]
, etc. – but it must be a set of something.
Fully persistent data structures are data structures, where old versions remain available after extensions or modifications.
To hide the primary constructor, it is possible to add a private
modifier in front of the class parameter list: class Queue[T] private (...)
. One possibility to create such a queue is to add a factory method by defining an object Queue
containing an apply
method:
object Queue {
def apply[T](...) = new Queue[T](...)
}
In Scala, generic types have by default nonvariant subtyping. This means, that a Queue[String]
would not be usable as a Queue[AnyRef]
. However, you can demand covariant (flexible) subtyping by prefixing the type parameter with a +
: trait Queue[+T] { ... }
. Bsides +
, there is also a -
prefix, which indicates contravariant subtyping. This means that if T
is a subtype of type S
, it would imply that Queue[S]
is a subtype of Queue[T]
.
Whether a type parameter is covariant, contravariant, or nonvariant is called the parameter's variance. The +
and -
symbols you can place next to type parameters are called variance annotations.
To define a lower bound for a type you have to use: U >: T
, which defines T
as the lower bound for U
. As a result, U
is required to be a supertype of T
.
A general principle in type system design: it is safe to assume that a type T
is a subtype of a type U
if you can substitute a value of type T
wherever a value of type U
is required. This is called the Liskov Substitution Principle. The principle holds if T
supports the same operations as U
and all of T
's operations require less and provide more than the corresponding operations in U
.
Abstract Members
Besides methods, you can also declare abstract fields and even abstract types as members of classes and traits.
The term abstract type in Scala means a type declared (with the type
keyword) to be a member of a class or trait, without specifying a definition: type T
You can think of a non-abstract (or, "concrete") type member as a way to define a new name, or alias, for a type. For example, in type T = String
the type String
is given the alias T
. As a result, anywhere T
appears, it means String
.
One reason to use a type member is to define a short, descriptive alias for a type whose real name is more verbose, or less obvious in meaning, than the alias. The other main use of type members is to declare abstract types that must be defined in subclasses.
An abstract val
declaration has a form like: val initial: String
. It gives a name and type for a val
, but not its value. This value has to be provided by a concrete val
definition in a subclass.
An abstract val
constrains its legal implementation: any implementation must be a val
definition, it may not be a var
or a def
. Abstract method declarations, on the other hand, may be implemented by both concrete method definitions and concrete val
definitions.
Like an abstract val
, an abstract var
declares just a name and a type, but not an initial value: var hour: Int
. If you declare an abstract var
, you implicitly declare an abstract getter method and an abstract setter method.
Using the keyword new
in front of a trait name, followed by a class body in curly braces, yields an instance of an anonymous class that mixes in the trait and is defined by the body. Example:
trait RationalTrait {
val numerArg: Int
val denomArg: Int
}
new RationalTrait {
val numerArg = 1
val denomArg = 2
}
A class parameter argument is evaluated before it is passed to the class constructor. An implementing val
definition in a subclass, by contrast, is evaluated only after the superclass has been initialized.
Pre-initialized fields let you initialize a field of a subclass before the superclass is called. To do this, simply place the field definition in braces before the superclass constructor call. Example:
val x = 2
new {
val numerArg = 1 * x
val denomArg = 2 * x
} with RationalTrait
Pre-initialized fields are not restricted to anonymous classes; they can also be used in objects or named subclasses.
If you prefix a val
definition with a lazy
modifier, the initializing expression on the right-hand side will only be evaluated the first time the val
is used.
When a class inherits from another, the first class is said to be a nominal subtype of the other one. It's a nominal subtype because each type has a name, and the names are explicitly declared to have a subtyping relationship. Scala additionally supports structural subtyping, where you get a subtyping relationship simply because two types have the same members. To get structural subtyping, use Scala's refinement types.
To define a refinement type, you simply write the base type, followed by a sequence of members listed in curly braces. The members in the curly braces further specify the types of members from the base class. Example of a type "animal that eats grass": Animal { type SuitableFood = Grass }
Implicit Conversions and Parameters
Implicit definitions are those that the compiler is allowed to insert into a program in order to fix any of its type errors.
The implicit
keyword is used to mark which declarations the compiler may use as implicits. You can use it to mark any variable, function, or object definition. Example: implicit def intToString(x: Int) = x.toString
The Scala compiler will only consider implicit conversions that are in scope. To make an implicit conversion available, therefore, you must in some way bring it into scope. Moreover, the implicit conversion must be in scope as a single identifier.
The compiler does not insert further implicit conversions when it is already in the middle of trying another implicit.
Whenever code type checks as it is written, no implicits are attempted. The compiler will not change code that already works.
Implicit conversions can have arbitrary names. The name of an implicit conversion matters only in two situations: if you want to write it explicitly in a method application, and for determining which implicit conversions are available at any place in the program.
Implicit conversion to an expected type is the first place the compiler will use implicits. The rule is simple. Whenever the compiler sees an X, but needs a Y, it will look for an implicit function that converts X to Y.
Implicit conversion also apply to the receiver of a method call, the object on which the method is invoked. One major use of receiver conversions is allowing smoother integration of new with existing types. They allow you to enable client programmers to use instances of existing types as it they were instances of your new type. The other major use of implicit conversions is to simulate adding new syntax.
The remaining place the compiler inserts implicits is within argument lists. The compiler will sometimes replace someCall(a)
with someCall(a)(b)
, or new SomeClass(a)
with new SomeClass(a)(b)
, thereby adding a missing parameter list to complete a function call.
As a word of warning, implicits can make code confusing if they are used too frequently. Thus, before adding a new implicit conversion, first ask whether you can achieve a similar effect through other means, such as inheritance, mixin composition, or method overloading.
Implementing Lists
Lists are not "built-in" as a language construct in Scala, they are defined by an abstract class List
in the scala
package, which comes with two subclasses for ::
and Nil
.
The Nil
object defines an empty list.
Class ::
, pronounced "cons" for "construct", represents non-empty lists.
For Expressions Revisited
A for
expression is of the form: for (seq) yield expr
. seq is a sequence of generators, definitions, and filters, with semicolons between successive elements. Example with one generator, one definition, and one filter:
for (p <- persons; n = p.name; if (n startsWith "To"))
yield n
You can also enclose the sequence in braces instead of parentheses. Then the semicolons become optional:
for {
p <- persons // a generator
n = p.name // a definition
if (n startsWith "To") // a filter
} yield n
A generator is of the form: pat <- expr
. The expression expr typically returns a list. The pattern pat gets matched one-by-one against all elements of that list. If the match succeeds, the variables in the pattern get bound to the corresponding parts of the element. If the match fails, no MatchError
is thrown. Instead, the element is simply discarded from the iteration.
A definition is of the form: pat < = expr
. It binds the pattern pat to the value of expr.
A filter is of the form: if expr
. expr is an expression of type Boolean
. The filter drops from the iteration all elements for which expr returns false
.
The Scala Collections API
By default, Scala always picks immutable collections.
Every kind of collection can be created by the same uniform syntax, writing the collection class name followed by its elements.
At the top of the collection hierarchy is the trait Traversable
. Collection classes implementing Traversable
just need to define a foreach
method, all other methods can be inherited.
The Seq
trait represents sequences. A sequence is a kind of iterable that has a length
and whose elements have fixed index positions, starting from 0.
Sets are Iterables
that contain no duplicate elements.
A SortedSet
is a set where, no matter what order elements were added to the set, the elements are traversed in sorted order.
Maps are Iterables
of pairs of keys and values. Implicit conversion lets you write key -> value
as an alternate syntax for the pair (key, value)
.
Lists are finite immutable sequences. They provide constant-time access to their first element as well as the rest of the list, and they have a constant-time cons operation for adding a new element to the front of the list. Many other operations take linear time.
A stream is like a list except that its elements are computed lazily. Because of this, a stream can be infinitely long. Streams are constructed with #::
. Example: val str = 1 #:: 2 #:: 3 #:: Stream.empty
.
Vectors give efficient access to elements beyond the head. Access to any elements of a vector take only "effectively constant time". They are immutable.
If you need a last-in-first-out sequence, you can use a Stack
. If you need a first-in-first-out sequence, you can use a Queue
.
A range is an ordered sequence of integers that are equally spaced apart. To create a range in Scala, use the predefined methods to
and by
. Example: 5 to 14 by 3
. To create a range that is exclusive of its upper limit, use the convenience method until
instead of to
.
A bit set represents a collection of small integers as the bits of a larger integer.
A list map represents a map as a linked list of key-value pairs. There is little usage for list maps in Scala because standard immutable maps are almost always faster.
An array buffer holds an array and a size. Most operations on an array buffer have the same speed as an array, because the operations simply access and modify the underlying array. Additionally, array buffers can have data efficiently added to the end. Appending an item to an array buffer takes amortized constant time. Thus, array buffers are useful for efficiently building up a large collection whenever the new items are always added to the end.
A list buffer is like an array buffer except that it uses a linked list internally instead of an array. If you plan to convert the buffer to a list once it is built up, use a list buffer instead of an array buffer.
Just like an array buffer is useful for building arrays, and a list buffer is useful for building lists, a string builder is useful for building strings.
Linked lists are mutable sequences that consist of nodes that are linked with next
pointers.
DoubleLinkedList
s are like single linked lists, except besides next
, they have another mutable field, prev
, that points to the element preceding the current node.
A MutableList
consists of a single linked list together with a pointer that refers to the terminal empty node of that list. This makes list append a constant time operation because it avoids having to traverse the list in search for its terminal node.
Array sequences are mutable sequences of fixed size that store their elements internally in an Array[AnyRef]
. They are implemented in Scala by the class ArraySeq
.
ArrayStack
is an alternative implementation of a mutable stack, which is backed by an Array
that gets resized as needed. It provides fast indexing and is generally slightly more efficient for most operations than a normal mutable stack.
A hash table stores its elements in an underlying array, placing each item at a position in the array determined by the hash code of that item. Iteration over a hash table is not guaranteed to occur in any particular order. To get a guaranteed iteration order, use a linked hash map or set instead of a regular one.
A weak hash map is a special kind of hash map in which the garbage collector does not follow links from the map to the keys stored in it. This means that a key and its associated value will disappear from the map if there is no other reference to that key.
A concurrent map can be accessed by several threads at once.
A class manifest is a type descriptor object that describes what the top-level class of a type is. The Scala compiler will generate code to construct and pass class manifests automatically if you instruct it to do so. "Instructing" means that you demand a class manifest as an implicit parameter: def evenElems[T](xs: Vector[T])(implicit m: ClassManifest[T]): Array[T] = ...
. Using an alternative and shorter syntax, you can also demand that the type comes with a class manifest by using a context bound. This means following the type with a colon and the class name ClassManifest
: def evenElems[T: ClassManifest](xs: Vector[T]: Array[T] = ...
.
Whenever you create an array of a type parameter T
, you also need to provide an implicit class manifest for T
.
The collection libraries have a uniform approach to equality and hashing. The idea is, first, to divide collections into sets, maps, and sequences. Collections in different categories are always unequal. For instance, Set(1, 2, 3)
is unequal to List(1, 2, 3)
even though they contain the same elements. On the other hand, within the same category, collections are equal if and only if they have the same elements. For example, List(1, 2, 3) == Vector(1, 2, 3)
.
Collections have quite a few methods that construct new collections. We call such methods transformers because they take at least one collection as their receiver object and produce another collection in their result. Transformers can be implemented in two principal ways: strict and non-strict (or lazy). A strict transformer constructs a new collection with all of its elements. A non-strict, or lazy, transformer constructs only a proxy for the result collection, and its elements are constructed on demand.
Scala collections are by default strict in all their transformers, except for Stream
, which implements all its transformer methods lazily. However, there is a systematic way to turn every collection into a lazy one and vice versa, which is based on collection views. A view is a special kind of collection that represents some base collection, but implements all of its transformers lazily.
An iterator is not a collection, but rather a way to access the elements of a collection one by one. The two basic operations on an iterator are next
and hasNext
.
Iterators behave like collections if you never access an iterator again after invoking a method on it.
Every iterator can be converted to a buffered iterator by calling its buffered
method. A buffered iterator provides a head
method, which will return its first element, but does not advance the iterator.
Every collection class in the Scala library has a companion object with an apply
method. This allows you to create collections with List(1, 2)
or Iterator(1, 2)
. Besides apply
, every collection companion object also defines a member empty
, which returns an empty collection.
The Architecture of Scala Collections
Almost all collection operations are implemented in terms of traversals and builders. Traversals are handled by Traversable
's foreach
method, and building new collections is handled by instances of class Builder
.
Scala's collections follow the "same-result-type" principle: wherever possible, a transformation method on a collection will yield a collection of the same type.
Extractors
An extractor in Scala is an object that has a method called unapply
as one of its members. The purpose of that unapply
method is to match a value and take it apart. Often, the extractor object also defines a dual method apply
for building values, but this is not required. Example:
object EMail {
def apply(user: String, domain: String) = user + "@" + domain
def unapply(str: String): Option[(String, String)] = {
val parts = str split "@"
if (parts.length == 2) Some(parts(0), parts(1)) else None
}
}
Scala lets you define a different extraction method specifically for vararg matching. This method is called unapplySeq
. The result type of unapplySeq
must conform to Option[Seq[T]]
, where the element type T
is arbitrary. Example:
object Domain {
def apply(parts: String*): String = parts.reverse.mkString(".")
def unapplySeq(whole: String): Option[Seq[String]] = Some(whole.split("\\.").reverse)
}
Simply append a .r
to a string to obtain a regular expression.
Annotations
Annotations are structured information added to program source code.
A typical use of an annotation looks like this: @deprecated def bigMistake() = //...
Annotations are allowed on any kind of declaration or definition, including val
s, var
s, def
s, class
es, object
s, trait
s, and type
s. The annotation applies to the entirety of the declaration or definition that follows it.
Annotations can also be applied to an expression. To do so, place a colon (:
) after the expression and then write the annotation: (e: @unchecked) match { ... }
The @deprecated
annotation will cause the Scala compiler to emit deprecation warnings whenever Scala code accesses the method. If you supply a string as an argument to @deprecated
, that string will be emitted along with the error message. Use this message to explain to developers what they should use instead of the deprecated method.
Working with XML
Scala lets you type in XML as a literal anywhere that an expression is valid. Simply type a start tag and then continue writing XML content. The compiler will go into an XML-input mode and will read content as XML until it sees the end tag matching the start tag you began with.
You are not restricted to writing out the exact XML you want. You can evaluate Scala code in the middle of an XML literal by using curly braces as an escape.
Modular Programming Using Objects
One advantage to using Scala source over XML for configuration is that the process of running your configuration file through the Scala compiler should uncover any misspellings in it prior to its actual use.
Object Equality
If two objects are equal according to the equals
method, then calling the hashCode
method on each of the two objects must produce the same integer result.
Combining Scala and Java
Scala is implemented as a translation to standard Java bytecodes. As much as possible, Scala features map directly onto the equivalent Java features.
For every Scala singleton object, the compiler will create a Java class for the object with a dollar sign added to the end.
Compiling any trait creates a Java interface of the same name.
All Java types have a Scala equivalent. This is necessary so that Scala code can access any legal Java class.
Actors and Concurrency
Actors are a good first tool of choice when designing concurrent software, because they can help you avoid the deadlocks and race conditions that are easy to fall into when using the shared data and locks model.
An actor is a thread-like entity that has a mailbox for receiving messages. To implement an actor, you subclass scala.actors.Actor
and implement the act
method. You start an actor by invoking its start
method.
Actors communicate by sending each other messages. You send a message by using the !
method: MyActor ! "a message"
.
When an actor sends a message, it does not block, and when an actor receives a message, it is not interrupted. The sent message waits in the receiving actor's mailbox until the actor calls receive
.
As soon as you go around the message passing scheme between actors, you drop back down into the shared data and locks model, with all the difficulties you were trying to avoid in the first place by using the actors model.
When you design a program that might involve parallelism in the future, whether using actors or not, you should try especially hard to make data structures immutable.
Combinator Parsing
Scala's parsing combinators are arranged in a hierarchy of traits, which are all contained in package scala.util.parsing.combinator
.
A clause such as id =>
immediately after the opening brace of a class template defines the identifier id
as an alias for this
in the class. It's as if you had written: val id = this
in the class body, except that the Scala compiler knows that id
is an alias for this
.
Aliasing can be a good abbreviation when you need to access the this
of an outer class. Example:
class Outer { outer =>
class Inner {
println(Outer.this eq outer) // prints: true
}
}
GUI Programming
To program with Swing, you need to import various classes from Scala's Swing API package: import scala.swing._
.
In Scala, subscribing to an event source source
is done by the call listenTo(source)
.
In Scala, an event is a real object that gets sent to subscribing components much like messages are sent to actors. For instance, pressing a button will create an event which is an instance of the case class ButtonClicked
.
To have your component react to incoming events you need to add a handler to a property called reactions
. Example:
reactions += {
case ButtonClicked(b) =>
// do something
}