Scala lets you program in a pleasantly concise style and at the same time "you have the assurance that you will not outgrow the language" [s. PIS p 42-43]. Because Scala was designed to be easily extended and adapted to the needs of the people programming it, it gives you convenience and flexibility at the same time. It allows you to implement your own abstractions that address new domains, yet still feel like native language support, i.e. you can write your own types, control constructs or even a whole domain specific language and their use would 'feel' just like using built-in types or language features.
Scala blends object-orientated and functional programming. In Scala really everything is an object, from primitives to function values, and every operation is a method call. For example, when you say 1+1
you are invoking the method +
defined in the class Int
on 1
- just as if you had written 1.+(1)
.
Scala is also a functional language, i.e. it supports the following two main ideas:
The first means that you can pass functions as arguments to other functions, return them as results and bind them to variables. It is also possible to define a function in another function or to use functions anonymously. These features are convenient means for abstracting over operations and creating new control structures. They provide great expressiveness, which leads to legible and succinct programs.
The second idea can also be stated in the way, that methods should not have any side effects. Functional programming usually encourages the use of immutable data structures and referentially transparent methods (for any given input the method call could be replaced by its result without affecting the programs semantics), but Scala gives you a choice here: When you want to, you can write in an imperative style with mutable data and side effects - i.e. writing statements rather than expressions - or you can choose to take the functional route and avoid imperative constructs.
[s. Programming in Scala 49ff]
Suereth (Scala in Depth) gives a quick overview over attributes commonly associated with the two different paradigms in Scala in Depth :
Object-orientated programming | Functional programming |
---|---|
Composition of objects | Compositions of functions |
Encapsulated stateful interaction | Deferred side effects [Immutable objects!] |
Iterative algorithms | Recursive algorithms |
Imperative flow | Lazy evaluation |
N/A | Pattern matching |
He illustrates this with two code examples that achieve the same results (a full cat) with different means:
class Bird
class Cat{
def catch(b:Bird): Unit = ...
def eat():Unit = ...
}
val cat = new Cat
val bird = new Bird
cat.catch(bird)
cat.eat
trait Cat
trait Bird
trait Catch
trait FullTummy
def catch(hunter: Cat, prey: Bird): Cat with Catch
def eat(consumer: Cat with Catch): Cat with FullTummy
val story = (catch _) andThen (eat _) // functional composition
story(new Cat, new Bird)
A good starting point would be the Scala documentation.
Twitter made a series of concise and succinct in-house lectures public: Scala School
There are numerous blogs, for example:
http://joelabrahamsson.com/programming/scala
http://www.codecommit.com/blog/scala/scala-for-java-refugees-part-1
http://danielwestheide.com/scala/neophytes.html (for the advanced learner)
A free eBook, if you like:
Odersky, M., Spoon, L. & Venners, B. 2008. Programming in Scala.
Or a free video tutorial that focusses on functional programming by Martin Odersky:
Functional Programming Principles in Scala
T <% Ordered[T]
match
x :: xs
xs.partition({ i => i.<(x) });
i => i.<(x)
that compares each element in tail to the pivot-element x quicksort(before).++(quicksort(after).::(x));
Scala's type-system has a separated sub-system with value-types (every type extending AnyVal
).
Scala supports the same types than Java (except of capitalisation):
Different to Java these types are defined as classes and operations (like +
, ==
, &&
, ...) are defined as functions on this classes.
A variable can be declared with the keyword var
:
var <variable_name> = <initial_value>
Local variables must have a initial value.
As you can see, in Scala you don't need an explicit type-declaration if the compiler can infer the right type. But if you need a specific type or the compiler might be unable to infer the type, you can always manually declare a type:
var <variable_name>[:<type>] = <initial_value>
The r
-declaration is a good example where a type-declaration can be needed: the compiler would have inferred the type Int
but if you strictly (for whatever reason) need or would like to have a Float
-value you can declare r
to be so.
If you declare a variable of some explicit type and want to initialize with a default-value, you can use the default-value _
. This default-value can be used for every type and gets converted into:
0
for number-typesfalse
for boolean variablesnull
for reference-typesA value can be declared with the keyword val
:
val <value_name>[:<type>] = <initial_value>
A value is a variable, that can only be assigned once.
As in the example of variables, value don't need an explicit type but can have one.
A constant in Scala can be declared with:
final val <constant_name>[:<type>] = <initial_value>
The difference between values and constants is, that a constant always returns the same result, whereas a value (for example a class attribute) can in some cases be overridden by a subclass.
A declaration of a class attribute is similar to a declaration of a local variable, value or constant:
var <attribute_name>[:<type>] = <initial_value>
val <value_name>[:<type>] = <initial_value>
final val <class_constant>[:<type>] = <initial_value>
Class attributes can be declared as variables, value or constants and can have explicit types.
Java knows a while
- and a do-while
-loop. Both structures are available in Scala, too. The syntax in Java and Scala is the same:
while(condition) {
statement
}
do {
statement
} while(condition)
Java knows two kinds of for
-loops:
Iterable
The first structure isn't much more than some additional syntactic sugar to a while
-loop.
for (declaration; condition; modifier) {
statement
}
is the same as
declaration
while (condition) {
statement
modifier
}
The second variant omits some 'counter' and uses an Iterator
and hence the following constructs are equivalent (except of the fact, that the variable it
isn't accessible):
for (Type tmp_name : iterable) {
statement
}
Iterator it = iterable.iterator();
while (it.hasNext()) {
Type tmp_name = it.next();
statement
}
To explain for
-loops in Scala, it is easier to start with the second variant. Scala uses (as usual) some other syntax but the semantic is the same:
for (tmp_name <- iterable) {
statement
}
The temporary variable can be declared together with some explicit type (compatible with the one of the Iterable
's elements) but needn't to.
Scala offers some possibility to use the Scala for
-loop much like the Java for
-loop with variable:
In Scala you can declare a Range
of numbers with can be seen as a list-like collection containing the elements between the given limits:
scala> (0 to 5)
res0: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3, 4, 5)
scala> (0 until 5)
res1: scala.collection.immutable.Range = Range(0, 1, 2, 3, 4)
This Range
is iterable and hence you can use the for
-loop like:
for (i <- 0 to 5) {
statement
}
which is pretty much like the Java's
for (int i = 0; i <= 5; i ++) {
statement
}
As you can see, the increment isn't specified in the Scala version, because Scala uses a default increment with +1. But you can add some increment-policy to the Range
:
for (i <- 0 to 5 by 2) {
statement
}
which would lead to executions with i
having the values 0, 2 and 4.
Using a policy, decrements are possible, too:
for (i <- 5 to 0 by -1) {
statement
}
Ranges aren't limited to integer numbers but can be used with floating point numbers (and floating point increments), too.
Within a for
-loop some filters can be applied:
for (i <- 0 to 100 if i % 2 == 0; if i % 3 == 0) {
statement
}
This examples shows a loop that will be executed each time i
is less or equal to 100 and is a multiple of 6 (checked by two different filters). To omit the semicolon, the parantheses can be replaced by curly braces and then the single statements can also be separated by linebreaks:
for {i <- 0 to 100
if i % 2 == 0
if i % 3 == 0} {
statement
}
Instead of using two nested for
-loops, this can be done by one for
-loop declaring two variables:
val lists = List(List(0, 1, 2, 3), List(-5, -3, 7), List(5), List(42, 23))
for {list <- lists
if list.length > 1
el <- list
if el > 0} {
println(el)
}
In this example the List of Lists gets iterated and every list containing more than one element (which means that List(5)
is skipped) gets iterated and finally every positive element gets handled and therefore printed.
for {x <- 0 to 100
y <- x to 100} {
statement
}
Using Scala's functional aspects for
-loops also can be written without any loop:
(0 to 5).foreach(println)
If you want to apply some function to every value of the 'counter'-variable, you can also declare the Range
and than pass the function and let the Range
apply the function to each element.
Or you can use the values to instantiate some class within some new Collection
:
scala> class Value(i: Int) {def get = i; override def toString() = "Value(" + i + ")"}
defined class Value
scala> (1 to 3).map(new Value(_)).toList
res2: List[Value] = List(Value(1), Value(2), Value(3))
The map
-function returns some (list-like) Collection
; the toList
-function then returns a List
.
To define a function you need the keyword def
:
def <function_name>(<parameter_list>)[:<return_type>] = {<function_body>}
where <parameter_list> := [<formal_parameter>: <type>]*
As a consequence of the fact that nearly everything in Scala is an expression, you don't need the keyword return
to specify the return-value. The function-body is the return-value. If your function is built up of multiple statements, the last one specifies the return-value (difference-example).
If your function should not have any return-value you can set the return-type to Unit
(the Scala equivalence to Java's void
):
def setX(x: Int): Unit = {...}
If the compiler can determine the function-body's result-type you can omit the return-type.
def setY(y: Int) = {...}
is equivalent the the one above if the last statement in the function-body is of type Unit
If your function doesn't have any parameters you can omit the braces, but the function's behavior depends on the kind of declaration:
add(4, 5)
a function-call of a not-parameterized function with braces also works like in Java:
nextRandomInt()
but: the expression nextRandomInt
can mean both the function (e.g. as parameter) or the function-call. The compiler analyzes the target-type and depending on this type either evaluates the function or simply returns the function-definition.
if the target-type is the result-type, the function gets evaluated
scala> val x: Int = nextRandomInt
x: Int = 5 // some random int
if the target-type is a function (with same signature), the function gets assigned
scala> val x: ()=> Int = nextRandomInt
x: () => Int = <function0>
if the target-type isn't explicitly given, the function gets evaluated
scala> val x = nextRandomInt
x: Int = 2 // some random int
if the target-type isn't suitable, the code doesn't compile
scala> val x: String = nextRandomInt
<console>:8: error: type mismatch;
found : Int
required: String
val x: String = nextRandomInt
^
getValue
returns the result (getValue()
does not compile)Scala supports higher-order functions. That means that one function (applyFunction
) can accept another function (e.g. add
or the given lambda-expression) as parameter. A function can be passed to another function by naming the function and omitting the braces. This works well for functions with braces at definition. For functions without braces you have to define some kind of anonymus function using the underscore (for example getValue _
).
A function can be represented by it's type. This type describes the argument types and return type:
Function-Type ::= FunctionArgs '=>' ReturnType
FunctionArgs ::= '(' [ ParamType {',' ParamType } ] ')'
Examples:
add
has type (Int, Int) => Int
because it returns an Int and accepts exactly two Int argumentsnextRandomInt
has type () => Int
because it returns an Int but doesn't accept argumentssetValue
has type (Int) => Unit
applyFunction
has type ((Int, Int) => Int) => ((Int, Int) => Int)
where the first argument is a function type itselffun(x: T1*): T2
where x: T1*
is the Scala counterpart to Java's T1... x
which both means, that any number of values of the given type can be passed in, would have type (Seq[T1]) => T2
Functions can be defined anonymously like the first parameter of the applyFunction
-application in exampleCalls
. The lambda-expression defined here is equivalent to the add function. As you can see, you define a lambda-expression by declaring the typed function-arguments and the function-body.
<parameter_list> => <function_body>
where <parameter_list>
is defined like it is for named function-definitions.
To apply a given function just pass the arguments to the function as you would do it with other defined functions.
A function-definition can have multiple braces with multiple arguments (as in the applyFunction
-example). This leads to functions that will be executed in several steps. If we take a look at applyFunction
: First, a "calculation rule" is passed to the function, which results in some mathematical function; Then the arguments can be passed to evaluate the function's result.
applyFuntion(add)
would result in a function with the signature (Int, Int) => Int
and would behave like add
.
Functions can be defined nested. That means that you can define a function inside another function. The nested definition is only visible inside the surrounding function.
The example is taken from Odersky, M.(2011). Scala by Example
If you would like to get a more thorough understanding of the example (and further refined versions of it), feel free to skim over its sixth chapter.
The fields numerator and denominator are defined as values, but could have been defined as parameterless methods as well: def numerator : Int = n / g
. The difference is, that as a value, the right-hand side is evaluated when the object is created and the method evaluates every time it is called.
Classes and objects can have the same name. If they are in the same source file, then the object is called a companion object
and the class companion class
. A companion object can access its classes private fields et vice versa. Scala classes cannot have static members. Any static methods you would have written in Java go into Singleton object(s) in Scala (the Java analogy to a companion object in Scala would be a class with static methods). They are invoked via the objects name, followed by a dot and the function name.
Objects do not define a type!
Keep traits short and try to express the smallest related idea possible to achieve greater modularization!
Java offers built-in support for enum-types, so that you can define something like:
public enum WeekDay {
Mon, Tue, Wed, Thu, Fri, Sat, Sun;
}
This comes like some sort of special class. Scala hasn't something like a class-type enum but Scala also supports Enums. In Scala you simply extend the class Enumeration
and create a value for every element and initialize it with Value
.
The type-declaration is a simple type-alias so that you can write Mon
instead of WeekDay.Mon
.
If you define an enum like in the WeekEnd
-example (taken from the Scala Api) each element has a String
-representation equivalent to the given name. If you want some other kind of representation, you can define the elements together with a specific String
like in the Size
-example.
Generics in Scala are semantically identical to Java. The notation differs from each other:
Collection<Type>
in Java equals Collection[Type]
in ScalaCollection
or Collection<?>
in Java equals Collection[_]
or Collection[T] forSome { type T }
in Scala Collection<? extends Number>
in Java equals Collection[_ <: Number]
or Collection[T] forSome { type T <: Number }
in Scalapublic <T extends Number> T add(T n1, T n2)
in Java equals def method[T <: Number](n1: T, n2: T): T
in ScalaScala offers the possibility to define types and to (re-)use them later. This types can be generic, too, like in the example of collType.
Pattern matching is a very powerful variant of Java's switch-statement.
Different to Java, exactly one case will always get executed. If two patterns would fit, the first one gets executed.
You can use pattern matching like switch-statements in Java (first example).
Note that the value SpecialCase
starts with a upper letter and that otherwise the case-clause wouldn't work as expected (the compiler could not find the value otherwise). You can combine several pattern using '|
' so that the case is executed if at least one of the pattern matches the given argument. The last pattern case _
is the Scala equivalent to Java's default
.
You can use pattern matching as a clean solution for type-checking instead of instanceof
-checks (for example for value-types as shown in the second example).
If the given value is of type Int
the first case matches and gets executed and the (Int
-)value is accessible via the identifier i
. If you only need the type but not the actual value, you can use the wildcard _
(Float
-example). The default case can be 'named', too, like in the ref
-line, so that you can try some further (manual) checks.
In addition to the patterns you can use so-called guards (additional conditions) for further specification.
You can replace the case i: Int ...
-line by the following ones to identify integer together with there sign:
case 0 => println("value was zero!")
case i: Int if i > 0 => println("value was a positive integer: " + i)
case i: Int => println("value was a negative integer: " + i)
The last case only gets executed for negative integer because positive integer and zero are handled previously and, as said above, only one case gets executed.
Besides the value and the type, the structure of some variable can be matched:
Pattern matching works also outside of match-statements:
Definitions of classes, traits, objects and members of this definitions (functions, attributes, etc) are public by default. But the visibility (and thereby the accessibility) can be limited to private or protected visibility. The visibilities are defined similarly to Java with some small differences:
this.protectedMember
At this point of knowledge, Scala seems less powerful than Java, but Scala offers the possibility to parameterize a visibility-modifier:
protected[class]
or private[class]
protected[package]
or private[package]
protected[this]
or private[this]
The same parameters are allowed for the private modifier, but private[scope] can be more stricly than protected[scope]:
protected
is (very much) the same as protected[classname]private
is (very much) the same as private[classname]private[packagename]
or protected[packagename]
defines a visibility similar to Java's package-visibilityprivate[this]
(or protected[this]
, which would be equivalent) for id
, the access v.id
in compare
would be impossibleprotected[packagename]
(or private[packagename]
) for name
, the access v.name
in copyNameFrom
in Subtype
would be possibleNote that Scala interprets packages in some kind of hierachy: Assume you have a package called outerPackage and you define a package outerPackage.innerPackage the innerPackage is a sub-package of the outerPackage and therefore classes in the innerPackage can access members of outerpackage with 'package' visibility, which would not be possible in Java.
Catching all Throwables with case e => ...
or case _ = ...
however is error-prone and should be avoided by any means. Control structures like break or continuations use ControlThrowables for flow control, so simply catching all Throwables would possibly break the code.
Let us have a look at a piece of code from Spiros Tzavellas Blog showing another reason, why you really should not catch all Throwables:
def containsEven(nums: String*): Boolean = {
try {
for (i <- nums) {
if (i.toInt % 2 == 0)
return true
}
} catch { case e => () }
false
}
You would expect the code to take an arbitrary number of strings and return true, if there is an even number among them, right?
Unfortunately, it always returns false, because the body of the for loop throws a NonLocalReturnControl that is caught by the enclosing try block.
That is because the for loop actually calls foreach
on nums
and passes the body of the for loop as a closure, in this case a Function1
. Because of the return, the closure triggers the return of the enclosing method instead of returning at the point of invocation. This is called a non-local return and is vital for control-flow abstractions using closures.
To fix the issue, we would better specify the exception that should be caught: case e : NumberFormatException
.
If you have to catch Throwable
for some reason by all means, the correct way to do it is to re-throw any ControlThrowable
in the catch-block.
Scala offers some very simple but powerful types:
In Scala you can pack multiple (1 to 22) values into one object, a so-called tuple.
The values, the tuple is built from, can be of different types and accessing one of this values will return the correct type. The compiler remembers, that:
tuple
is of type Int
Boolean
String
To access some value from a tuple, the tuple offeres some attributes _1
, _2
, ... depending on the number of values given at creation-time.
A method can take and return tuples and hence there is no need of defining any return-value-data-container, a method somehow can return mutliple values and not just one (swap-example)
Scala offers the generic type Option[A]
that is inspired by the Null Object pattern. An instance of Option[A]
can either be None
or Some(A)
. Lets take a method with return-type Option[A]
as example:
The caller now can get such an optional return-value and depending on the kind of return-value can act in different ways (e.g. via pattern matching).
The third type presented here is the generic type Either[A, B]
. An instance of Either[A, B]
can either be Left(A)
or Right(B)
. Very similar to Option
the value can have two completely different 'types', but by using Either
you can for example return either some value or an exception (or message) (Option
only offered the possibility to indicate failure, Either
additionally can give further information).
This solution allows us to reuse our low-level mechanism.