Introduction
Scala is object-oriented: every value is an object.
- classes
- traits
- mixin-based composition as a clean replacement for multiple inheritance.
Scala is functional: every function is a value.
- higher-order functions
- nested functions
- currying (multiple parameter lists)
- case classes
- pattern matching model algebraic types used in many functional programming languages.
- Singleton objects provide a convenient way to group functions that aren’t members of a class.
Scala is statically typed
the type system supports:
- Type inference
- generic classes
- variance annotations
- upper and lower type bounds,
- inner classes and abstract types as object members
- compound types
- explicitly typed self references
- implicit parameters and conversions
- polymorphic methods
Scala is extensible (domain-specific language)
- Implicit classes allow adding extension methods to existing types.
- String interpolation is user-extensible with custom interpolators.
Scala interoperates
- interoperate with the Java Runtime Environment (JRE).
Basics
| variables | |
|---|---|
var x = 5 |
variable |
GOODval x = 5 BAD x=6 |
constant |
var x: Double = 5 |
explicit type |
| functions | |
|---|---|
GOODdef f(x: Int) = { x*x } BAD def f(x: Int) { x*x } |
define function hidden error: without = it’s a Unit-returning procedure; causes havoc |
GOODdef f(x: Any) = println(x) BAD def f(x) = println(x) |
define function syntax error: need types for every arg. |
type R = Double |
type alias |
def f(x: R) vs. def f(x: => R) |
call-by-value call-by-name (lazy parameters) |
(x:R) => x*x |
anonymous function |
(1 to 5).map(_*2) vs. (1 to 5).reduceLeft( _+_ ) |
anonymous function: underscore is positionally matched arg. |
(1 to 5).map( x => x*x ) |
anonymous function: to use an arg twice, have to name it. |
GOOD(1 to 5).map(2*) BAD (1 to 5).map(*2) |
anonymous function: bound infix method. Use 2*_ for sanity’s sake instead. |
(1 to 5).map { x => val y=x*2; println(y); y } |
anonymous function: block style returns last expression. |
(1 to 5) filter {_%2 == 0} map {_*2} |
anonymous functions: pipeline style. (or parens too). |
def compose(g:R=>R, h:R=>R) = (x:R) => g(h(x)) val f = compose({_*2}, {_-1}) |
anonymous functions: to pass in multiple blocks, need outer parens. |
val zscore = (mean:R, sd:R) => (x:R) => (x-mean)/sd |
currying, obvious syntax. |
def zscore(mean:R, sd:R) = (x:R) => (x-mean)/sd |
currying, obvious syntax |
def zscore(mean:R, sd:R)(x:R) = (x-mean)/sd |
currying, sugar syntax. but then: |
val normer = zscore(7, 0.4) _ |
need trailing underscore to get the partial, only for the sugar version. |
def mapmake[T](g:T=>T)(seq: List[T]) = seq.map(g) |
generic type. |
5.+(3); 5 + 3 (1 to 5) map (_*2) |
infix sugar. 5 + 3 means 5.+(3) |
def sum(args: Int*) = args.reduceLeft(_+_) |
varargs. |
Unified Types
Classes
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Traits
Traits are used to share interfaces and fields between classes. Classes and objects can extend traits but traits cannot be instantiated and therefore have no parameters.
Defining a trait
|
|
Using traits
Use the extends keyword to extend a trait. Then implement any abstract members of the trait using the override keyword:
|
|
Subtyping
Where a given trait is required, a subtype of the trait can be used instead.
|
|
Tuples
A tuple is a class that can hold elements of different types. Tuples are immutable.
|
|
Tuple in Scala is a series of classes: Tuple2, Tuple3, etc., through Tuple22.
Accessing the elements
Tuple elements are accessed using underscore syntax. ‘tuple._n’ gives nth element.
|
|
Destructuring tuple data
|
|
Tuple destructuring can be used in pattern matching too.
|
|
in ‘for’ comprehension
|
|
Users may sometimes find hard to choose between Tuples and case classes. As a rule, case classes are preferred choice if elements carry more meaning.
Class Composition with Mixins
Mixins are traits which are used to compose a class.
|
|
Class D has a superclass B and a mixin C. Classes can only have one superclass but many mixins (using the keywords extends and with respectively). The mixins and the superclass may have the same supertype.
Higher-order Functions
Nested Methods
Multiple Parameter Lists (Currying)
Case Classes
|
|
|
|
Pattern Matching
Singleton Objects
Objects are single instances of their own definitions.
|
|
Regular Expression Patterns
Extractor Objects
For Comprehensions
Generic Classes
Variances
Upper Type Bounds
Lower Type Bounds
The term B >: A expresses that the type parameter B or the abstract type B refer to a supertype of type A. In most cases, Awill be the type parameter of the class and B will be the type parameter of a method.
|
|
This program does not compile because the parameter elem in prepend is of type B, which we declared covariant. This doesn’t work because functions are contravariant in their parameter types and covariant in their result types.
To fix this, we need to flip the variance of the type of the parameter elem in prepend. We do this by introducing a new type parameter U that has B as a lower type bound.
|
|
Now we can do the following:
|
|
The Node[Bird] can be assigned the africanSwallowList but then accept EuropeanSwallows.
Inner Classes
In Scala inner classes are bound to the outer object.
|
|
The class Node is a path-dependent type because it is nested in the class Graph. Therefore, all nodes in the connectedNodes must be created using the newNode from the same instance of Graph.
|
|
The nodes of the other graph have a different type.
|
|
Java let inner classes are members of the enclosing class. In Scala such a type can be expressed as well, it is written Graph#Node.
|
|
Abstract Types
Traits and abstract classes can have an abstract type member. This means that the concrete implementations define the actual type.
|
|
We can extend this trait in an abstract class, adding an upper-type-bound to T to make it more specific.
|
|
Traits or classes with abstract type members are often used in combination with anonymous class instantiations.
|
|
Here the factory newIntSeqBuf uses an anonymous class implementation of IntSeqBuf (i.e. new IntSeqBuffer), setting type T to a List[Int].
It is also possible to turn abstract type members into type parameters of classes and vice versa. Here is a version of the code above which only uses type parameters:
|
|
Note that we have to use variance annotations here (+T <: Seq[U]) in order to hide the concrete sequence implementation type of the object returned from method newIntSeqBuf. Furthermore, there are cases where it is not possible to replace abstract types with type parameters.
Compound Types
Sometimes it is necessary to express that the type of an object is a subtype of several other types. In Scala this can be expressed with the help of compound types, which are intersections of object types.
|
|
This compound type is written like this in Scala: Cloneable with Resetable.
|
|
Self-type
Self-types are a way to declare that a trait must be mixed into another trait, even though it doesn’t directly extend it. That makes the members of the dependency available without imports.
A self-type is a way to narrow the type of this or another identifier that aliases this.
To use a self-type in a trait, write an identifier, the type of another trait to mix in, and a => (e.g. someIdentifier: SomeOtherTrait =>).
|
|
Because we said this: User => in trait Tweeter, now the variable username is in scope for the tweet method. This also means that since VerifiedTweeter extends Tweeter, it must also mix-in User(using with User).
Implicit Parameters
A method can have an implicit parameter list, marked by the implicit keyword at the start of the parameter list. If the parameters in that parameter list are not passed as usual, Scala will look if it can get an implicit value of the correct type, and if it can, pass it automatically.
The places Scala will look for these parameters fall into two categories:
- Scala will first look for implicit definitions and implicit parameters that can be accessed directly (without a prefix) at the point the method with the implicit parameter block is called.
- Then it looks for members marked implicit in all the companion objects associated with the implicit candidate type.
|
|
To show how implicit parameters work, we first define monoids StringMonoid and IntMonoid for strings and integers, respectively. The implicit keyword indicates that the corresponding object can be used implicitly.
Making the parameter m implicit here means we only have to provide the xs parameter when we call the method if Scala can find a an implicit Monoid[A] to use for the implicit m parameter.
Implicit Conversions
Polymorphic Methods
Type Inference
Operators
In Scala, operators are methods. Any method with a single parameter can be used as an infix operator.
|
|
|
|
Defining and using operators
You can use any legal identifier as an operator. This includes a name like add or a symbol(s) like +.
|
|
Using parentheses, you can build up complex expressions with readable syntax.
|
|
Precedence
|
|
For example
|
|
Is equivalent to
|
|
?^ has the highest precedence because it starts with the character ?. + has the second highest precedence, followed by ==>, ^?, |, and less.
By-name Parameters
By-name parameters are only evaluated when used. They are in contrast to by-value parameters. To make a parameter called by-name, simply prepend => to its type.
|
|
If the condition is false, the body is never evaluated because we prepended => to the type of body.
This ability to delay evaluation of a parameter until it is used can help performance if the parameter is computationally intensive to evaluate or a longer-running block of code such as fetching a URL.