| 3.1 Values | 39 | 
| 3.2 Loops, Conditionals, Comprehensions | 46 | 
| 3.3 Methods and Functions | 50 | 
| 3.4 Classes and Traits | 53 | 
for (i <- Range.inclusive(1, 100)) {
  println(
    if (i % 3 == 0 && i % 5 == 0) "FizzBuzz"
    else if (i % 3 == 0) "Fizz"
    else if (i % 5 == 0) "Buzz"
    else i
  )
}
Snippet 3.1: the popular "FizzBuzz" programming challenge, implemented in Scala
This chapter is a quick tour of the Scala language. For now we will focus on the basics of Scala that are similar to what you might find in any mainstream programming language.
The goal of this chapter is to get familiar you enough that you can take the same sort of code you are used to writing in some other language and write it in Scala without difficulty. This chapter will not cover more Scala-specific programming styles or language features: those will be left for Chapter 5: Notable Scala Features.
For this chapter, we will write our code in the Ammonite Scala REPL:
$ amm
Loading...
Welcome to the Ammonite Repl 2.2.0 (Scala 2.13.2 Java 11.0.7)
@
</> 3.2.bashScala has the following sets of primitive types:
| 
 | 
 | 
These types are identical to the primitive types in Java, and would be similar
to those in C#, C++, or any other statically typed programming language. Each
type supports the typical operations, e.g. booleans support boolean logic ||&&+-*/|&==!=
Numbers default to 32-bit Int*/+-
@ 1 + 2 * 3
res0: Int = 7
@ (1 + 2) * 3
res1: Int = 9
</> 3.3.scalaIntLongL have a bigger range and do not overflow as easily:
|  |  | 
Apart from the basic operators, there are a lot of useful methods on
java and .lang.Integerjava:.lang.Long
@ java.lang.Integer.<tab>...
@ java.lang.Integer.toBinaryString(123)
res6: String = "1111011"
@ java.lang.Integer.numberOfTrailingZeros(24)
res7: Int = 3
</> 3.6.scala64-bit Double1.01.0FFloat
@ 1.0 / 3.0
res8: Double = 0.3333333333333333
@ 1.0F / 3.0F
res9: Float = 0.33333334F
</> 3.7.scala32-bit FloatDoublejava and
.lang.Floatjava have a similar set of useful operations you can perform on
.lang.DoubleFloatDouble
StringChar
@ "hello world"
res10: String = "hello world"
</> 3.8.scalaString.substring+s"..." and interpolating the values with $ or $:{...}
|  |  | 
You can name local values with the val
@ val x = 1
@ x + 2
res18: Int = 3
</> 3.11.scalaNote that valval xvar
|  |  | 
In general, you should try to use valvalvar
Both valvar
|  |  | 
Tuples are fixed-length collections of values, which may be of different types:
@ val t = (1, true, "hello")
t: (Int, Boolean, String) = (1, true, "hello")
@ t._1
res27: Int = 1
@ t._2
res28: Boolean = true
@ t._3
res29: String = "hello"
</> 3.16.scalaAbove, we are storing a tuple into the local value t using the (a, b, c)._1._2._3
The type of the local value t can be annotated as a tuple type:
@ val t: (Int, Boolean, String) = (1, true, "hello")
You can also use the val (a, b, c) = t
|  |  | 
Tuples come in any size from 1 to 22 items long:
@ val t = (1, true, "hello", 'c', 0.2, 0.5f, 12345678912345L)
t: (Int, Boolean, String, Char, Double, Float, Long) = (
  1,
  true,
  "hello",
  'c',
  0.2,
  0.5F,
  12345678912345L
)
</> 3.19.scalaMost tuples should be relatively small. Large tuples can easily get confusing:
while working with ._1._2._3._11._13
Arrays are instantiated using the Array syntax, and entries within
each array are retrieved using [T](a, b, c)a:(n)
@ val a = Array[Int](1, 2, 3, 4)
@ a(0) // first entry, array indices start from 0
res36: Int = 1
@ a(3) // last entry
res37: Int = 4
@ val a2 = Array[String]("one", "two", "three", "four")
a2: Array[String] = Array("one", "two", "three", "four")
@ a2(1) // second entry
res39: String = "two"
</> 3.20.scalaThe type parameter inside the square brackets [Int][String](1, 2, 3, 4)a rather than square brackets (3)a as is common in
many other programming languages.[3]
You can omit the explicit type parameter and let the compiler infer the Array's
type, or create an empty array of a specified type using new Array[T](length)
|  |  | 
For Arrays created using new Array0falseBooleannullStringArrays are mutable but fixed-length: you can change the value of
each entry but cannot change the number of entries by adding or removing values.
We will see how to create variable-length collections later in Chapter 4: Scala Collections.
Multi-dimensional arrays, or arrays-of-arrays, are also supported:
@ val multi = Array(Array(1, 2), Array(3, 4))
multi: Array[Array[Int]] = Array(Array(1, 2), Array(3, 4))
@ multi(0)(0)
res47: Int = 1
@ multi(0)(1)
res48: Int = 2
@ multi(1)(0)
res49: Int = 3
@ multi(1)(1)
res50: Int = 4
</> 3.23.scalaMulti-dimensional arrays can be useful to represent grids, matrices, and similar values.
Scala's Option type allows you to represent a value that may or may not
exist. An [T]Option can either be [T]Some indicating that a value is
present, or (v: T)None indicating that it is absent:
@ def hello(title: String, firstName: String, lastNameOpt: Option[String]) = {
    lastNameOpt match {
      case Some(lastName) => println(s"Hello $title. $lastName")
      case None => println(s"Hello $firstName")
    }
  }
@ hello("Mr", "Haoyi", None)
Hello Haoyi
@ hello("Mr", "Haoyi", Some("Li"))
Hello Mr. Li
</> 3.24.scalaThe above example shows you how to construct Options using Some and None,
as well as matchOptions rather than nullOptions force you to handle both cases of present/absent, whereas when using
nullNullPointerExceptions at runtime. We will go deeper into pattern
matching in Chapter 5: Notable Scala Features.
Options contain some helper methods that make it easy to work with the
optional value, such as getOrElse, which substitutes an alternate value if the
Option is None:
@ Some("Li").getOrElse("<unknown>")
res54: String = "Li"
@ None.getOrElse("<unknown>")
res55: String = "<unknown>"
</> 3.25.scalaOptions are very similar to a collection whose size is 01.map
|  |  | 
Above, we combine .map.getOrElse-1
For-loops in Scala are similar to "foreach" loops in other languages: they
directly loop over the elements in a collection, without needing to explicitly
maintain and increment an index. If you want to loop over a range of indices,
you can loop over a Range such as Range:(0, 5)
|  |  | 
You can loop over nested Arrays by placing multiple <-
@ val multi = Array(Array(1, 2, 3), Array(4, 5, 6))
@ for (arr <- multi; i <- arr) println(i)
1
2
3
4
5
6
</> 3.30.scalaLoops can have guards using an if
@ for (arr <- multi; i <- arr; if i % 2 == 0) println(i)
2
4
6
</> 3.31.scalaifelseifelsea  ternary expressions in other languages.
Scala does not have a separate ternary expression syntax, and so the ? b : cifelsetotal  below.+=
|  |  | 
Now that we know the basics of Scala syntax, let's consider the common "Fizzbuzz" programming challenge:
Write a short program that prints each number from 1 to 100 on a new line.
For each multiple of 3, print "Fizz" instead of the number.
For each multiple of 5, print "Buzz" instead of the number.
For numbers which are multiples of both 3 and 5, print "FizzBuzz" instead of the number.
We can accomplish this as follows:
@ for (i <- Range.inclusive(1, 100)) {
    if (i % 3 == 0 && i % 5 == 0) println("FizzBuzz")
    else if (i % 3 == 0) println("Fizz")
    else if (i % 5 == 0) println("Buzz")
    else println(i)
  }
1
2
Fizz
4
Buzz
Fizz
7
8
Fizz
Buzz
11
Fizz
13
14
FizzBuzz
...
</> 3.34.scalaSince ifelse
@ for (i <- Range.inclusive(1, 100)) {
    println(
      if (i % 3 == 0 && i % 5 == 0) "FizzBuzz"
      else if (i % 3 == 0) "Fizz"
      else if (i % 5 == 0) "Buzz"
      else i
    )
  }
</> 3.35.scalaApart from using forforyield
@ val a = Array(1, 2, 3, 4)
@ val a2 = for (i <- a) yield i * i
a2: Array[Int] = Array(1, 4, 9, 16)
@ val a3 = for (i <- a) yield "hello " + i
a3: Array[String] = Array("hello 1", "hello 2", "hello 3", "hello 4")
</> 3.36.scalaSimilar to loops, you can filter which items end up in the final collection
using an if
@ val a4 = for (i <- a if i % 2 == 0) yield "hello " + i
a4: Array[String] = Array("hello 2", "hello 4")
</> 3.37.scalaComprehensions can also take multiple input arrays, a and b below. This
flattens them out into one final output Array, similar to using a nested
for-loop:
@ val a = Array(1, 2); val b = Array("hello", "world")
@ val flattened = for (i <- a; s <- b) yield s + i
flattened: Array[String] = Array("hello1", "world1", "hello2", "world2")
</> 3.38.scalaYou can also replace the parentheses (){}<-
@ val flattened = for{
    i <- a
    s <- b
  } yield s + i
flattened: Array[String] = Array("hello1", "world1", "hello2", "world2")
@ val flattened2 = for{
    s <- b
    i <- a
  } yield s + i
flattened2: Array[String] = Array("hello1", "hello2", "world1", "world2")
</> 3.39.scalaWe can use comprehensions to write a version of FizzBuzz that doesn't print its
results immediately to the console, but returns them as a Seq (short for
"sequence"):
@ val fizzbuzz = for (i <- Range.inclusive(1, 100)) yield {
    if (i % 3 == 0 && i % 5 == 0) "FizzBuzz"
    else if (i % 3 == 0) "Fizz"
    else if (i % 5 == 0) "Buzz"
    else i.toString
  }
fizzbuzz: IndexedSeq[String] = Vector(
  "1",
  "2",
  "Fizz",
  "4",
  "Buzz",
...
</> 3.40.scalaWe can then use the fizzbuzz collection however we like: storing it in a
variable, passing it into methods, or processing it in other ways. We will cover
what you can do with these collections later, in Chapter 4: Scala Collections.
You can define methods using the def
@ def printHello(times: Int) = {
    println("hello " + times)
  }
@ printHello(1)
hello 1
@ printHello(times = 2) // argument name provided explicitly
hello 2
</> 3.41.scalaPassing in the wrong type of argument, or missing required arguments, is a compiler error. However, if the argument has a default value, then passing it is optional.
|  |  | 
Apart from performing actions like printing, methods can also return values. The
last expression within the curly brace {}
@ def hello(i: Int = 0) = {
    "hello " + i
  }
@ hello(1)
res96: String = "hello 1"
</> 3.44.scalaYou can call the method and print out or perform other computation on the returned value:
@ println(hello())
hello 0
@ val helloHello = hello(123) + " " + hello(456)
helloHello: String = "hello 123 hello 456"
@ helloHello.reverse
res99: String = "654 olleh 321 olleh"
</> 3.45.scalaYou can define function values using the =>
@ var g: Int => Int = i => i + 1
@ g(10)
res101: Int = 11
@ g = i => i * 2
@ g(10)
res103: Int = 20
</> 3.46.scalaNote that unlike methods, function values cannot have optional arguments (i.e.
with default values) and cannot take type parameters via the [T]
In general, you should prefer using methods unless you really need the flexibility to pass as parameters or store them in variables. But if you need that flexibility, function values are a great tool to have.
One common use case of function values is to pass them into methods that take
function parameters. Such methods are often called "higher order methods".
Below, we have a class Box with a method printMsg that prints its contents
(an Intupdate that takes a function of type Int => Intx. You can then pass a function literal into
update in order to change the value of x:
|  |  | 
Simple functions literals like i  can also be written via the
shorthand => i + 5_ , with the underscore + 5_ standing in for the function
parameter.
@ b.update(_ + 5)
@ b.printMsg("Hello")
Hello11
</> 3.49.scalaThis placeholder syntax for function literals also works for multi-argument
functions, e.g. (x, y) => x + y_ .+ _
Any method that takes a function as an argument can also be given a method
reference, as long as the method's signature matches that of the function type,
here Int => Int
@ def increment(i: Int) = i + 1
@ val b = new Box(123)
@ b.update(increment) // Providing a method reference
@ b.update(x => increment(x)) // Explicitly writing out the function literal
@ b.update{x => increment(x)} // Methods taking a single function can be called with {}s
@ b.update(increment(_)) // You can also use the `_` placeholder syntax
@ b.printMsg("result: ")
result: 127
</> 3.50.scalaMethods can be defined to take multiple parameter lists. This is useful for
writing higher-order methods that can be used like control structures, such as
the myLoop method below:
|  |  | 
The ability to pass function literals to methods is used to great effect in the standard library, to concisely perform transformations on collections. We will see more of that in Chapter 4: Scala Collections.
You can define classes using the classnew(x: Int)x is thus accessible in the
printMsg function, but cannot be accessed outside the class:
|  |  | 
To make x publicly accessible you can make it a valvar
|  |  | 
|  |  | 
You can also use valvar
|  |  | 
traitinterfaces in traditional object-oriented languages: a
set of methods that multiple classes can inherit. Instances of these classes can
then be used interchangeably.
@ trait Point{ def hypotenuse: Double }
@ class Point2D(x: Double, y: Double) extends Point{
    def hypotenuse = math.sqrt(x * x + y * y)
  }
@ class Point3D(x: Double, y: Double, z: Double) extends Point{
    def hypotenuse = math.sqrt(x * x + y * y + z * z)
  }
@ val points: Array[Point] = Array(new Point2D(1, 2), new Point3D(4, 5, 6))
@ for (p <- points) println(p.hypotenuse)
2.23606797749979
8.774964387392123
</> 3.61.scalaAbove, we have defined a Point trait with a single method def hypotenuse: DoublePoint2D and Point3D both have different sets of
parameters, but they both implement def hypotenusePoint2Ds and Point3Ds into our points and treat them all
uniformly as objects with a : Array[Point]def hypotenuse
In this chapter, we have gone through a lightning tour of the core Scala language. While the exact syntax may be new to you, the concepts should be mostly familiar: primitives, arrays, loops, conditionals, methods, and classes are part of almost every programming language. Next we will look at the core of the Scala standard library: the Scala Collections.
Exercise: Define a def flexibleFizzBuzzString => Unitprintln the
output directly, or store the output in a previously-allocated array they
already have handy.
|  |  | 
Exercise: Write a recursive method printMessages that can receive an array of Msg
class instances, each with an optional parent ID, and use it to print out a
threaded fashion. That means that child messages are print out indented
underneath their parents, and the nesting can be arbitrarily deep.
class Msg(val id: Int, val parent: Option[Int], val txt: String)
def printMessages(messages: Array[Msg]): Unit = ...
| 
 | 
 | 
Exercise: Define a pair of methods withFileWriter and withFileReader that can be
called as shown below. Each method should take the name of a file, and a
function value that is called with a java or
.io.BufferedReaderjava that it can use to read or write data. Opening and
closing of the reader/writer should be automatic, such that a caller cannot
forget to close the file. This is similar to Python "context managers" or Java
"try-with-resource" syntax..io.BufferedWriter
TestContextManagers.scwithFileWriter</> 3.67.scala("File.txt"){writer=>writer.write("Hello\n");writer.write("World!")}valresult=withFileReader("File.txt"){reader=>reader.readLine()+"\n"+reader.readLine()}assert(result=="Hello\nWorld!")
You can use the Java standard library APIs
java and .nio.file.Files.newBufferedWriternewBufferedReader for working
with file readers and writers. We will get more familiar with working with
files and the filesystem in Chapter 7: Files and Subprocesses.