This section should be most of the things you need to get going. The goal is that you can read most scala code and write your own simple applications. (Also check out Intermediate and Advanced)
Scala has a REPL. Simply enter scala
from the command line:
$ scala
...
> 1 + 3
res0: Int = 4
res0
is a variable that the REPL defines. It has the type Int
and
the value of 4
. You can actually interact with this afterwards:
> res0 + 4
res1: Int = 8
The Scala REPL can do nearly anything. If you need to do more than one-liners, you can enter paste mode:
> :paste
def sum(a: Int, b: Int) = {
a + b
}
// CTRL-D
> sum(3, 4)
res0: Int = 7
To leave the REPL, use :q
There are two ways to build a simple scala application. The first is similar to Java and should be familiar if you know Java:
// HelloWorld.scala
object HelloWorld {
def main(args: Array[String]) {
println("Hello, world!")
}
}
Throw this into IntelliJ and you should be able to run it. You can also use the command line:
$ scalac HelloWorld.scala
$ scala HelloWorld
Hello, world!
The other way (and simpler way) is simply to extend App
:
// HelloWorld.scala
object HelloWorld extends App {
println("Hello, world!")
}
The {}
block for the object simply acts as a constructor, so when you
extend App
it treats the block as the application starting point.
It is pretty useful to be able to look up API docs for Scala.
Located here: http://www.scala-lang.org/api/2.11.8/
For example, try searching for "List" and look through some of the methods.
To create an immutable (constant) value in Scala:
val x = 5
x
cannot be reassigned after this.
To create a variable:
var y = 5
y = 6
It is idiomatic in Scala to use val
whenever possible and avoid var
.
Scala has print
and println
. println
simply adds a newline at the
end.
print("Name: ")
println("Martin Odersky")
if (counter == 5) {
println("It's five!")
} else if (counter == 10) {
println("It's ten!")
} else {
println("I don't know what it is!")
}
if
statements actually return a value in Scala. In this way, they can
replace ternaries:
val greeting = if (isGuest) { "Welcome, stranger!" } else { "Welcome back!" }
You can even remove the braces in this case:
val greeting = if (isGuest) "Welcome, stranger!" else "Welcome back!"
Scala has both for
and while
.
for (i <- 1 to 10) {
println(i)
}
1 to 10
actually translates to 1.to(10)
(explained later) and
generates a sequence of the numbers from 1 to 10. The for loop iterates
through this sequence one number at a time, assigning each to i
.
While loop:
var counter = 0
while (counter < 10) {
println(counter)
counter += 1
}
Loops are certainly useful, but typically there are better options in Scala (explained later).
Functions are first-class citizens in Scala. They can be defined, assigned to variables, passed as a function parameter, and returned from a function.
Without using any syntactic sugar:
def sum(x: Int, y: Int): Int = {
x + y
}
The last statement in a function block is the returned value for the
function. So return
is not necessary. However, return
still exists
in certain situations - such as breaking from a loop inside a function.
Also, notice that the definition is assigned to the block using an equals
sign. The =
can be omitted for functions that don't return a value.
With these functions the return type would be Unit
.
However, you can remove the function type if Scala can infer it. You can also remove the braces if the function is a single expression:
def sum(x: Int, y: Int) = x + y
Call the function like this:
val summed = sum(3, 4) // 7
You can also assign an anonymous function to a variable:
val isLong = { (input: String) => input.length > 5 }
isLong("Hi") // false
isLong("Hello, world!") // true
The =>
syntax is the separator between parameters and function body.
In the previous example, you can also remove the braces for the function
definition.
Function parameters must always be listed with their type. Scala can't infer this.
Functions can have default parameters:
def say(statement: String, name: String = "dude"): Unit = {
println(statement + ", " + name)
}
Since the function is Unit
type, it can be written without the type
and without the equals sign:
def say(statement: String, name: String = "dude") {
println(statement + ", " + name)
}
Called like this:
say("Welcome home", "Nick") // Welcome home, Nick
or like this:
say("What's up") // What's up, dude
or even like this:
say("C'mon", name = "Scoob") // C'mon, Scoob
With named parameters, you have the ability to choose which default arguments to provide if there is more than one argument that has a default.
List
is the fundamental immutable collection in Scala.
Create:
val empty = List[Int]() // empty list (type sometimes needed)
val integers = List(1,2,3) // with values (type optional)
Access:
val two = integers(1)
Appending/Prepending:
val list1 = List(1) :+ 2 // List(1, 2)
val list2 = 1 :: List(2) // List(1, 2)
val list3 = List(1) ++ List(2) // List(1, 2)
Note that all these operations create a new list, since the original list is immutable. Mutable collections also exist, but will be covered later.
There are also many ways to manipulate a collection. The most used are mapping, filtering, and folding.
Mapping is used to modify elements in a list by applying a function to each element.
val nums = List(1, 2, 3, 4, 5)
val countByTwo = nums.map({ (num: Int) => num * 2 }) // 2, 4, 6, 8, 10
map
takes a function and the =>
notation defines one in place. However,
there are other options. You can use an underscore instead of defining
a parameter. You can also remove the parentheses or the braces (but only one!)
val words = List("Tom", "Jerry", "Richard")
val lengths = words.map(_.length) // 3, 5, 7
Using an underscore only works if you are using the parameter one time.
Filtering is used to create a new list with a subset of the source list.
val words = List("Car", "Vehicle", "TV", "Television")
val longWords = words.filter { _.length > 5 } // Vehicle, Television
Folding is used to reduce a list to a single value (or collection). It is extremely powerful and is incredibly common in the functional paradigm. Folding can do anything a while loop can do.
val nums = List(1, 2, 3, 4, 5)
val sum = nums.foldLeft(0) { (res, n) => res + n } // 15
foldLeft
is a curried function, meaning it takes more than one set
of parameters. The first set is just the value where you want the fold
to start. The second set is a binary function (takes two arguments)
that produces a single value (the final result, iteratively).
So, to peek into the steps of the fold for this example:
Step 1: res = 0, n = 1, res + n = 1, remaining = 2, 3, 4, 5
Step 2: res = 1, n = 2, res + n = 3, remaining = 3, 4, 5
Step 3: res = 3, n = 3, res + n = 6, remaining = 4, 5
Step 4: res = 6, n = 4, res + n = 10, remaining = 5
Step 5: res = 10, n = 5, res + n = 15, remaining = Nil
The final value (15) is what is returned from the fold. This fold can also be put much more simply:
(((((0 + 1) + 2) + 3) + 4) + 5)
The 0
value comes from the first parameter set to the function. It can
also be omitted if you want to use the first index of the list as the
initial value. Use reduceLeft
for this. For example:
val nums = List(1, 2, 3, 4, 5)
val sum = nums.reduceLeft { (res, n) => res + n }
This time, the first call to our function has the value 1
for res
.
Which looks like this:
((((1 + 2) + 3) + 4) + 5)
You can also change out the =>
notation and use underscores. Also,
a fold can return any type, including another list.
val words = List("Mary", "had", "a", "little", "magnanimous", "lamb")
val allShort: Boolean = words.foldLeft(true) { _ && _.length < 10 } // false :(
Here, the first underscore represents the value from the previous
iteration (starting with true
). The second parameter is the next value
(one of the strings in words
). If you remove "magnanimous" from the
list, the result should be true.
An example of returning another list:
val words = List("Bob", "Frank", "Tim")
val lengths = words.foldLeft(List[Int]()) { (col, el) => col :+ el.length } // List(3, 5, 3)
Of course, this is a contrived example. It would be much simpler to just
use map
. You can also substitute the =>
notation here, but this is
hopefully less confusing.
In this example, the first time the function is called, it is called
with res
equal to the new integer list (List[Int]()
) and el
equal to
the first element in words
("Bob"
). It then creates a new list with
the length of "Bob"
appended to the original list and returns it (List(3)
)
and the process continues - adding the lengths of "Frank"
and "Tim"
to the list in the next iterations.
Also note that if you want to return a value that is a different type
than the elements in the list, you will need to use foldLeft
instead
of reduceLeft
.
Note: There is also a reduceRight
and a foldRight
that works by
starting from the end of the sequence.
Scala has both mutable and immutable Map
options. The default Map is
immutable, but you can access the other one with collection.mutable.Map
.
Create:
val empty = Map[Int, String]()
val map = Map(1 -> "Hare", 2 -> "Turtle")
Access:
map(1) // Hare
Append:
val threePlaces = map + (3 -> "Cricket") // (1 -> Hare, 2 -> Turtle, 3 -> Cricket)
val fourPlaces = map + (3 -> "Cricket", 4 -> "Fox") // (1 -> Hare, 2 -> Turtle, 3 -> Cricket, 4 -> Fox)
val fivePlaces = map ++ List(3 -> "Cricket", 4 -> "Fox", 5 -> "Snake") // (1 -> Hare, 2 -> Turtle, 3 -> Cricket, 4 -> Fox)
Notice the double plus (++) when using a List.
Update:
val updated = map + (2 -> "Fox") // (1 -> Hare, 2 -> Fox)
When adding a key to a map, it will overwrite the previous key if it exists.
Remove:
val noWinner = updated - 1 // (2 -> Turtle, 3 -> Fox)
val oneWinner = updated - (2, 3) // (1 -> Hare)
val justThird = updated -- List(1, 2) // (3 -> Fox)
Notice the double minus (--) when using a List.
With a collection.mutable.Map
you can update an item like this:
map(2) = "Bear" // (1 -> Hare, 2 -> Bear)
A tuple is any number of values surrounded by parenthesis and separated by commas.
val item = ("Chips", 1.25)
val price = item._2 // access an index (1-indexed, not 0-indexed)
val (name, _) = item // name assigned to "Chips" by pattern matching
for (i < -1 to 10) yield i * 2
for (i <- 1 to 10; j <- 11 to 20) { }
// guards, yield, etc...
def sum(nums: Int*) = nums.sum
:_* operator