Scala Tutorial
Scala is a general-purpose programming language providing support for functional programming and a strong static type system. Designed to be concise, many of Scala’s designs aimed to address the criticisms of Java.
- Scala is static type system (do type checking at compile-time), and also offer some form of type inference, the capability of the type system to deduce the type of a variable.
- Scala source code is intended to be compiled to Java bytecode, runs on JVM. Scala provides language interoperability with Java.
- Scala.js is a Scala compiler that complies to JavaScript, and making it possible to write Scala programs that run in web browsers.
Java vs. Scala Sample
// Java:
public class Point {
private final double x, y;
public Point(final double x, final double y) {
this.x = x;
this.y = y;
}
public Point(
final double x, final double y,
final boolean addToGrid
) {
this(x, y);
if (addToGrid)
grid.add(this);
}
public Point() {
this(0.0, 0.0);
}
public double getX() {
return x;
}
public double getY() {
return y;
}
double distanceToPoint(final Point other) {
return distanceBetweenPoints(x, y,
other.x, other.y);
}
private static Grid grid = new Grid();
static double distanceBetweenPoints(
final double x1, final double y1,
final double x2, final double y2
) {
return Math.hypot(x1 - x2, y1 - y2);
}
}
// Scala
class Point(
val x: Double, val y: Double,
addToGrid: Boolean = false) {
import Point._
if (addToGrid)
grid.add(this)
def this() = this(0.0, 0.0)
def distanceToPoint(other: Point) =
distanceBetweenPoints(x, y, other.x, other.y)
}
object Point {
private val grid = new Grid()
def distanceBetweenPoints(x1: Double, y1: Double,
x2: Double, y2: Double) = {
math.hypot(x1 - x2, y1 - y2)
}
}
Syntactic Differences
- Scala does not require semicolons to end statements.
- Value types are capitalized:
Int, Double, Boolean
instead ofint, double, boolean
. - Parameter and return types follow, as in Pascal, rather than precede as in C.
- Methods must be preceded by
def
. - Local or class variables must be preceded by
val
(immutable variable) orvar
(mutable variable). - The
return
operator is unnecessary in a function; the value of the last executed statement or expression is normally the function’s value. - Instead of the Java cast operator
(Type) foo
, Scala usesfoo.asInstanceOf[Type]
, or a specialized function such astoDouble
ortoInt
. - Instead of Java’s
import foo.*
; Scala usesimport foo._
. - Function or method
foo()
can also be called as justfoo
; methodthread.send(signo)
can also be called as justthread send signo
; and methodfoo.toString()
can also be called as justfoo toString
. - Array references are written like function calls, e.g.
array(i)
rather thanarray[i]
. (Internally in Scala, both arrays and functions are conceptualized as kinds of mathematical mappings from one object to another.) - Generic types are written as e.g.
List[String]
rather than Java’sList<String>
. - Instead of the pseudo-type
void
, Scala has the actual singleton classUnit
.
Conceptual Differences
- Scala has no static variables or methods. Instead, it has singleton objects which are essentially classes with only one object in the class. It is common to place static variables and methods in a singleton object with the same name as the class name, which is then known as a companion object. (The underlying class for the singleton object has a
$
appended, Hence, forclass Foo
with companion objectobject Foo
, under the hood there’s a classFoo$
containing the companion object’s code, and one object of this class is created, using the singleton pattern). - In place of constructor parameters, Scala has class parameters, which are placed on the class, similar to parameters to a function. When declared with a
val
orvar
modifier, fields are also defined with the same name, and automatically initialized from the class parameters. (Under the hood, external access to public fields always goes through accessor (getter) and mutator (setter) methods, which are automatically created. The accessor function has the same name as the field, which is why it’s unnecessary in the above example to explicitly declare accessor methods.) Node that alternative constructors can also be declared, as in Java. Code that would go into the default constructor (other than initializing the member variables) goes directly at class level. - Default visibility in Scala is public.
Syntactic Flexibility
- Semicolons are unnecessary; lines are automatically joined if they begin or end with a token that cannot normally come in this position, or if they are unclosed parentheses or brackets.
- Any method can be used as an infix operator, e.g.
"%d apples".format(num)
and"%d apples" format num
are equivalent. In fact, arithmetic operators like+
and<<
are treated just like any other methods, since function names are allowed to consist of sequences of arbitrary symbols (with a few exceptions made for things like parens, brackets and braces that must be handled specially); the only special treatment that such symbol-named methods undergo concerns the handling of precedence. - Methods apply and update have syntactic short forms.
foo()
– Wherefoo
is a value (singleton object or class instance) – is short forfoo.apply()
, andfoo() = 42
is short forfoo.update(42)
. Similarly,foo(42)
is short forfoo.apply(42)
, andfoo(4) = 2
is short forfoo.update(4, 2)
. This is used for collection classes and extends to many other cases, such as STM cells. - Scala distinguishes between no-parens (
def foo = 42
) and empty-parens (def foo() = 42
) methods. When calling an empty-parens method, the parentheses may be omitted, which is useful when calling into Java libraries that do not know this distinction, e.g., usingfoo.toString
instead offoo.toString()
. By convention, a method should be defined with empty-parens when it performs side effects. - Method names ending in colon (
:
) expect the argument on the left-hand-side and receiver on the right-hand-side. For example, the4::2::Nil
is the same asNil.::(2).::(4)
, the first form corresponding visually to the result (a list with first element 4 and second element 2). - Class body variables can transparently implemented as separate getter and setter methods. For
trait FooLike { var bar: Int }
, an implementation may beobject Foo extends FooLike { private var x = 0; def bar = x; def bar_=(value: Int) { x = value } }
. The call side will still be able to use a concisefoo.bar = 42
. - The use of curly braces instead of parentheses is allowed in method calls. This allows pure library implementations of new control structures. For example,
breakable { ... if (...) break() ... }
looks as ifbreakable
was a language defined keyword, but really is just a method taking a thunk argument. Methods that take thunks or functions often place these in a second parameter list, allowing to mix parentheses and curly braces syntax:Vector.fill(4) { math.random }
is the same asVector.fill(4) ( math.random )
. The curly braces variant allows the expression to span multiple lines. - For-expressions can accommodate any type that defines monadic methods such as
map
,flatMap
andfilter
. - Unified type system. In Scala, all types inherit from a top-level class Any, whose immediate children are
AnyVal
(value types, such asInt
andBoolean
) andAnyRef
(reference types, as in Java). This means that the Java distinction between primitive types and boxed types (e.g.int
vs.Integer
) is not present in Scala; boxing and unboxing is completely transparent to the user.
For-expressions
Scala has a much more powerful concept of for-expressions.
val s = for (x <- 1 to 25 if x * x > 50) yield 2 * x
Vector(16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50)
Below example shows expression (mention, times) <- mentions
is an example of pattern matching. Iterating over a map returns a set of key-value tuples which can be easily destructured into separate variables for the key and value.
// Given a map specifying Twitter users mentioned in a set of tweets,
// and number of times each user was mentioned, look up the users
// in a map of known politicians, and return a new map giving only the
// Democratic politicians (as objects, rather than strings).
val dem_mentions = for {
(mention, times) <- mentions
account <- accounts.get(mention)
if account.party == "Decoratic"
} yield (account, times)
Functional Tendencies
While supporting all of the object-oriented features available in Java, Scala also provides a large number of capabilities that are normally found only in functional programming languages.
- No distinction between statements and expressions
All statements are in fact expressions that evaluate to some value. Return the type Unit or Nothing.
// Java:
int hexDigit = x >= 10 ? x + 'A' - 10 : x + '0';
// Scala:
val hexDigit = if (x >= 10) x + 'A' - 10 else x + '0'
All expressions are functions, even methods that return Unit
are written with an equals sign.
def printValue(x: String): Unit = {
println("I ate a %s".format(x))
}
def printValue(x: String) = println("I ate a %s" format x)
- Type inference
Due to type interface, the type of variables, function return values, and many other expressions can typically be omitted, as the compiler can deduce it. And certain types still need to be declared (most notably, function parameters, and the return types of recursive functions).
def formatApples(x: Int) = "I ate %d apples".format(x)
def factorial(x: Int): Int =
if (x == 0)
1
else
x * factorial(x - 1)
- Anonymous functions with capturing semantics (i.e., closures)
In Scala, functions are objects, and a convenient syntax exists for specifying anonymous functions. An example is the expression x => x < 2
. An even shorter form of anonymous function uses placeholder variables: list map { x => sqrt(x) }
can be written more concisely as list map { sqrt(_) }
or even list map sqrt.
- Immutable variables and objects
The immutable variants allow for very easy concurrency - no locks are needed as no shared objects are ever modified. Immutable structures are also constructed efficiently, in the sense that modified instances reuses most of old instance data and unused parts are collected by GC.
- Lazy (non-strict) evaluation
Scala evaluates expressions as soon as they are available, rather than as needed. However, it is possible to declare a variable non-strict (“lazy”) with the lazy
keyword, meaning that the code to produce the variable’s value will not be evaluated until the first time the variable is referenced.
- Delimited continuations
- Higher-order functions
- Nested functions
- Currying
- Pattern matching
def qsort(list: List[Int]): List[Int] = list match {
case Nil => Nil // only matches the literal object Nil
case pivot :: tail => // matches a non-empty lists
val (smaller, rest) = tail.partition(_ < pivot)
qsort(smaller) ::: pivot :: qsort(rest)
}
In the pattern-matching example above, the body of the match
operator is a partial function, which consists of a series of case
expressions. As below, we a ready-only variable is declared whose type is a function from lists of integers to lists of integers, and bind it to a partial function.
val qsort: List[Int] => List[Int] = {
case Nil => Nil
case pivot :: tail =>
val (smaller, rest) = tail.partition(_ < pivot)
qsort(smaller) ::: pivot :: qsort(rest)
}
- Algebraic data types (through case classes)
- Tuples
Object-oriented Extensions
Scala is a pure object-oriented language in the sense that every value is an object. Data types and behaviors of objects are described by classes and traits.
Traits are Scala’s replacement for Java’s interfaces.
abstract class Window {
// abstract
def draw()
}
class SimpleWindow extends Window {
def draw() {
println("in SimpleWindow")
// draw a basic window
}
}
trait WindowDecoration extends Window {}
trait HorizontalScrollbarDecoration extends WindowDecoration {
// "abstract override" is needed here in order for "super()" to work because the parent
// function is abstract. If it were concrete, regular "override" would be enough.
abstract override def draw() {
println("in HorizontalScrollbarDecoration");
super.draw()
// now draw a horizontal scroll bar
}
trait VerticalScrollbarDecoration extends WindowDecoration {
abstract override def draw() {
println("in VerticalScrollbarDecoration")
super.draw()
// now draw a vertical scrollbar
}
}
trait TitleDecoration extends WindowDecoration {
abstract override def draw() {
println("in TitleDecoration");
super.draw()
// now draw the title bar
}
}
// A variable may be declared thus:
val mywin = new SimpleWindow with VerticalScrollbarDecoration with HorizontalScrollbarDecoration with TitleDecoration
}
The result of calling mywin.draw()
is
in TitleDecoration
in HorizontalScrollbarDecoration
in VerticalScrollbarDecoration
in SimpleWindow
Type Enrichment
This technique allows new methods to be added to an existing class using an add-on library such that only code the imports the add-on library gets the new functionality, and all other code is unaffected.
object MyExtensions {
implicit class IntPredicates(i: Int) {
def isEven = i % 2 == 0
def isOdd = !isEven
}
}
import MyExtensions._ // bring implicit enrichment into scope
println(4.isEven) // -> true
Concurrency
Scala standard library includes support for the actor model, in addition to the standard Java concurrency APIs. Akka is a seperate open source framework that provides actor-based concurrency.
Scala also comes with built-in support for data-parallel programming in the form of Parallel Collections.
val urls = List("http://scala-lang.org", "https://github.com/scala/scala")
def fromURL(url: String) = scala.io.Source.fromURL(url).getLines().mkString("\n")
val t = System.currentTimeMillis()
urls.par.map(fromURL(_))
println("time: " + (System.currentTimeMillis - t) + "ms");
Custer Computing
The most well-known open-source cluster computing solution written in Scala is Apache Spark and Apache Kafka.