Chapter 9 - Scala and mathlib

Learning how to read and write code is quite similar to learning how to use math to formalise verbal theories. In this chapter we cover the basics of coding in Scala and mathlib. Understanding Scala code might seem daunting at first, but you have already mastered a more difficult skill: Translating verbal theory (which is underspecified) to formal, mathematical characterizations. Translating from formalizations to simulation code requires understanding Scala, but it is easier in the sense that there is much less ambiguity to deal with.

At the end of this chapter you will be able to understand how concepts from Chapter 3 - Math concepts and notation can be translated to Scala code. The chapter is quite detailed and we recommend to use it as a reference guide when reading chapters on simulations or when writing your own code. The cheat sheet down below provides a helpful overview.

Scala fundamentals

We cover only a small subset of the full Scala language here. This subset suffices for implementing basic simulations of formal theories. If after reading this chapter you want to learn more about Scala, there are various textbooks (e.g. Odersky, Spoon and Venners, 2019; but see also Scala books) and online resources such as the official Tour of Scala and Creative Scala.

Variables, functions and types

The basic expressions in a functional programming language such as Scala are variables, functions and types. Variables, like their mathematical counterpart, are containers that can store a value.Variable is not completely accurate terminology here. val stores a value which is (in functional programming terminology) immutable, i.e., it cannot change. It is more like a constant, rather than a variable. In this book, we will rarely use mutable var variables. For example:

val myNumber = 3

Let’s break this example down into its parts. The word val tells the computer that you will specify a value, which is a container storing a constant (in this case the integer 3). The identifier (name) of this value is the word myNumber. The assignment operator = takes the value on its right side and stores is in the container on the left side.

Any container in Scala has a type. For example, the value myNumber above has type Int which stands for integer. Scala can often derive these types automatically, but for clarity you may want to use explicit types:

val mySecondNumber: Int = 2

Types are helpful. They provide a safety net against programming mistakes since you cannot assign a value that is incompatible with the specified type. Running the following code results in a type mismatch error and the compiler (i.e., the software that interprets and runs your code) will even tell you which type it expected and which type you provided.

val mySecondNumber: Int = 3.9

Functions allow us to write code that takes input, one or more arguments, and returns output. For example, addition \(add(x, y) = x+y\) can be coded as:

def add(x: Int, y: Int): Int = {
  x + y
}

add(3, 4)

The word def specifies that we are constructing a function with the identifier add. The comma-separated list between parantheses is the list of arguments you can pass to this function, where each argument has a specified type. Functions require that the type of the output is explicitly defined, in this example Int. Then the assignment operator = links the body of function which is delineated by curly brackets. Whenever we call this function with the right arguments, the value of the body is computed relative to the arguments and that value is the output of the function.

Question 9.1

Fill in the blanks in the code below and write a function that computes the following equation: \(f(a, b, c) = a + b * c\)

Hint?
def equation(...): Int = {
  ???
}

equation(2, 5, -1) == -3    // Test the function, true if correct.

Functions in Scala have types too which becomes clearer with the following alternative notation.

def add: (Int, Int) => Int = (x: Int, y: Int) => {
  x + y
}
add(3, 4)

This notation is not used often since it is hard to read, but it explicitly defines the function’s type which is very similar to how we express function types mathematically:

Scala (Int, Int) => Int
Math \(add: \mathbb{N} \times \mathbb{N} \rightarrow \mathbb{N}\)

In functional programming languages like Scala you can even pass a function as an argument of another function. This is a powerful way to organize your code and very useful in writing simulation code that is closely tied to formal theories. In a sense, a theory is a function itself, mapping a list of arguments (input) to output. We’ve seen that some formal theories can have functions as arguments. For example, Selecting invitees (version 1) in Chapter 4 takes as input the function \(like: P \times P \rightarrow \{true, false\}\). Here is a (partial) example of a function as argument.

def selectingInvitees(..., like: (Person, Person) => Boolean)

We return to selecting invitees in the next chapter, where we will use this coding strategy. For now, we should note one syntactic oddity in the Scala language. Sometimes when you pass a function as an argument, the compiler will complain with the following message:

error: missing argument list for method myFun
Unapplied methods are only converted to functions when a function type is expected.
You can make this conversion explicit by writing `myFun _` or `myFun(_)` instead of `myFun`.

The solution for this problem is often following the instructions in the error explicitly and add an underscore _ to the function. For example:

selectingInvitees(..., like)    // Procudes error.
selectingInvitees(..., like _)  // Correct.

Finally, while we won’t go into details of object-orientation here, it is useful to know that some functions accompany certain types. For example, the type String has functions built-in that can be called with the dot-notation. These functions (also called methods) have access to the value they are called upon. The following example called method toUpperCase that evaluates to the upper-case version of the original string.

"This is a String.".toUpperCase

Auto-complete can be triggered with Ctrl-space or Cmd-space in Scalafiddle which we use in this book. Other development tools often use the same key-command, or alternatively the Tab key. Many Scala development tools allow you to look through the list of methods by using auto completion. Just add a . to a value and access auto complete to open the list.

Blocks and scope

We have implicitly used the notions of block and scope, but how are they defined? In Scala a block is a sequence of expressions *delineated by curly brackets. A block has a value which is the value of the *last statement in the block.

{
  val a = 3
  val b = 6
  a + b       // This block evaluates to 9 with type Int.
}

Blocks can be nested, but values and functions defined within a block cannot be accessed outside that block. This property is known as scope, accessing values or functions out of scope results in a compiler error:

{
  val x = 3

  {
    val y = 6
    x + y       // Valid, x and y are in this block's scope.
  }

  x + y         // Invalid, y is outside this block's scope.
}

Conditional

The conditional expression is more colloquially known as the if-then-else expression. It allows for branching paths of code, depending on the truth value of the conditional. An example (try changing the value of x):

val x = 4
if(x % 2 == 0) {
  println("X is even.")
} else {
  println("X is odd.")
}

Each part of the conditional consists of a code block, though for single expressions the curly brackets to simplify the code:

val x = 4
if(x % 2 == 0) println("X is even.")
else println("X is odd.")

You can have an arbitrary number of branching paths using else if:

val x = 4
if(x < 0) println("X is negative.")
else if(x == 0) println("X is zero.")
else if(x <= 10) println("X is small.")
else println("X is large.")

The conditional expression is a block with nested blocks. This means that it evaluates to the value of the last expression in the block that is evaluated by the conditional. This behaviour is useful when defining a function whose output is computed differently depending on some truth condition. For example, a function that computes the absolute value of x multiplies x with -1 if x is negative and otherwise evaluates to x.

Question 9.2

Complete the code below by implementing the body of abs(x: Int): Int . The function should evaluate to the absolute (positive) value of x.

Hint?
def abs(x: Int): Int = {
  ???
}

abs(-10) == 10    // Test the function, true if correct.

Basic types

Scala comes with a plethora of types and datastructures, many of which fall beyond the scope of this book. However, the following basic types and their operators will be very useful to know.

Type Math equivalent Example value
Int \(\mathbb{N}\) 3
Double \(\mathbb{R}\) 2.7
Boolean \(\{true, false\}\) true
Char n.a. ‘c’
String n.a. “Awesome”

Int and Double share many operators such as addition +, subtraction -, multiplication * and division /. The library Math also contains several useful functions which you can apply with the dot notation:

val x: Double = 3
val y: Double = 6

println(x + y)
println(x - y)
println(x * 3)
println(y / 2)            // Division.
println(y % 2)            // Remainder or modulo.
println(Math.pow(x, y))   // Exponentiation, x^y.
println(Math.min(x, y))
println(Math.max(x, y))

For Boolean types these are some common expressions:

println(true && true)   // Logical and.
println(true || false)  // Logical or.
println(true ^ true)    // Logical xor.
println(!true)          // Negation.

And these can be combined with numbers like so:

val x: Double = 3
val y: Double = 6

println(x <= 3 && x > 1)          // Number between 1 and 3 (inclusive).
println(x % 2 == 0 || x > 0)      // Positive even number.
println(x % 2 == 0 ^ y % 2 == 0)  // Either x or y is even, not both.
println(!(x < 0))                 // x is positive.

If you change in the code example above Double to Int you might notice that there is a certain compatility between the two types. That is, the compiler does not give a type error when you add a double to an integer, even though the addition function expects two doubles or two integers. This property is known as polymorphism and is an advanced topic. For the curious, see this overview on Wikipedia. This is because Scala knows it can convert an integer to a double value, which it will automatically do.

val x: Int = 3
val y: Double = 2.5
x+y    // Evaluates to a Double value 5.5

Some types cannot be converted and when you try to mix these, the compiler will let you know with an error:

val x: Int = 3
val b: Boolean = true
x + b    // How to add a Boolean to an Int?

This conversion also kicks in when calling a function with compatible arguments:

def add(a: Double, b: Double): Double = a +b
add(1.4, 3)
Question 9.3

This conversion often only works one way. It is not possible to convert a Double to an Int without loosing information. Observe what happens when you run the code example above after changing the type of the arguments of add to Int.

Lists, sets and tuples

When you want to store multiple values you can use collections. Example collections could be the temperature forecast for the next seven days:

22.2 °C
23.1 °C
23.7 °C
22.3 °C
24.3 °C
24.7 °C
25.1 °C

Or the people that you know: Erik, Lamar, Angelica, Emanuel, Lorraine, Meghan, Myron, Erica, Lester, Javier, Kelly, Abraham, Lindsay, Harriet, and Guadalupe. Or the cost of a menu card item: Vegan pie costs €9,90.

Some collections, like the temperature forecast and menu card item, are ordered: one value follows the next. In math, these are expressed in a list or sequence \(\langle 22.2, 23.1, 23.7, 22.3, 24.3, 24.7, 25.1 \rangle\) or a tuple \((\text{vegan pie}, 9.9)\). Unordered collections, such as people, are expressed in a set \(\{\text{Erik},\) \(\text{Lamar},\) \(\text{Angelica},\) \(\text{Emanuel},\) \(\text{Lorraine},\) \(\text{Meghan},\) \(\text{Myron},\) \(\text{Erica},\) \(\text{Lester},\) \(\text{Javier},\) \(\text{Kelly},\) \(\text{Abraham},\) \(\text{Lindsay},\) \(\text{Harriet},\) \(\text{Guadalupe}\}\).

In Scala we can store ordered collections in a List or tuple and unordered collections in a Set:

val forecast = List(22.2, 23.1, 23.7, 22.3, 24.3, 24.7, 25.1)
val menuItem = ("Vegan pie", 9.90)
val people   = Set("Erik", "Lamar", "Angelica", "Emanuel", "Lorraine",
                   "Meghan", "Myron", "Erica", "Lester", "Javier", "Kelly",
                   "Abraham", "Lindsay", "Harriet", "Guadalupe")

We’ll dive into sets below using the mathlib library, so let’s first get some familiarity with lists. Some basic examples are in the code block below. Try playing around with them to see what they do.

val forecastThisWeek = List(22.2, 23.1, 23.7, 22.3, 24.3, 24.7, 25.1)
val forecastNextWeek = List(22.3, 19.8, 18.4, 18.0, 17.6, 17.5, 17.2)

println(23.1 :: forecastThisWeek)               // Prepend element.
println(forecastThisWeek ::: forecastNextWeek)  // Prepend list.
println(forecastThisWeek.size)                  // Number of elements in list.
println(forecastThisWeek.contains(23.7))        // Does the list contain element?
println(forecastThisWeek.head)                  // The first element of the list.
println(forecastThisWeek.tail)                  // Everything except the first element.
println(forecastThisWeek(3))                    // The n-th element.
println(forecastThisWeek.isEmpty)               // Checks whether the list is emtpy.

The power of collections lies in being able to apply to all of the elements. The idea is that if we have a function that applies to one element, e.g., \(sq(x) = x^2)\), we can apply it to all elements in the list. The most common of these applications is called map. It takes as argument a function f and evaluates to a list where each element computed using f:

list 1 2 3 4
  \(\downarrow\) \(\downarrow\) \(\downarrow\) \(\downarrow\)
  sq(1) sq(2) sq(3) sq(4)
  \(\downarrow\) \(\downarrow\) \(\downarrow\) \(\downarrow\)
list.map(sq) 1 4 9 16
// Function that computes the square root of x.
def sq(x: Int): Int = x * x
val list = List(1, 2, 3, 4)

println(list.map(sq))

The type of the argument of the function must be the same type as the elements in the list, but its output can be of any type. For example, take an Int and return a String.

// Function that creates a String with x "x"s.
def xx(x: Int): String = {
  if(x==1) "x"
  else "x" + xx(x-1)
}
val list = List(1, 2, 3, 4)

println(list.map(xx))

What other useful things can we do with lists? Below are some self-explanatory examples.

// Function that checks whether x is even.
def isEven(x: Int): Boolean = {
  x % 2 == 0
}
val list = List(1, 2, 3, 4)

println(list.exists(isEven))    // Does list contain an element that isEven?
println(list.exists(_ > 3))     // Implicit function, does the list contain an element larger than 3?
println(list.forall(isEven))    // Do all elements in list return true for isEven?
println(list.forall(_ <= 100))  // Implicit function, does the list contain an element larger than 3?
println(list.filter(isEven))    // Filter out all elements that return true for isEven.
println(list.filter(_ < 3))     // Filter out all elements less than 3.

There are some special methods for lists that contain numbers:

// Function that checks whether x is even.
val list = List(1, 2, 3, 4)

println(list.sum)             // The sum of all numbers in list.
println(list.product)         // The product of all numbers in list.

Many of the functions and methods that work for lists, also work for other collections such as sets. Even a String can be treated as a collection as it is essentially a list of characters.

Question 9.4

Look at the code scaffold below. Implement the body of the function consonant and choose the method to apply to sentence such that the code evaluates to the sentence with only consonants.

Hint?
val sentence = "Hi, you're doing awesome!"
val vowels   = List('a', 'e', 'i', 'o', 'u')

def consonant(character: Char): Boolean = {
  ???
}

sentence.___(consonant)

Generics

Collections in Scala are what is called generic. The change their type depending on the values you apply to them. The list containing the forecast consists of values of type Int, therefore the type of forecast is List[Int]. In Scala, the type of a generic includes the type of its contents denoted between square brackets.

Question 9.5

What is the type of vowels? Fill in the blanks: List[___]

Hint?
Stop and think

What benefits do we get from generics?

Collections, courtesy of being generic, can store different types. A set of doubles? No problem Set[Double]. A list of Booleans? Easy Set[Boolean]! The type of a collection also grants a level of protection to the programmer. You cannot prepend a Boolean to a list of integers:

val forecast: List[Double]  = List(22.2, 23.1, 23.7, 22.3, 24.3, 24.7, 25.1)
val forecast2: List[Double] = true :: forecast

If you could mix different (incompatible) types in a collection, performing calculations on that collection would be either undefined (e.g., what does mean to compute the sum of a list of Int, Boolean and String?) or it would have unpredictable effects.

mathlib

mathlib is a library written to support the development of simulations of formal theories (computational-level models specifically). Admittedly, an exact implementation is not always possible. Especially when the simulation deviates from the formal theory, it is important to be able for experts to understand how the simulation is different. Ideally, we want (experts) to be able to read simulation code and understand that it is an exact implementation of the formal theory. As you may have noticed during the Scala introduction, functional programming is closely related to the mathematical language of formal theories. Consider the following example (toy) formal theory:


Pizza Toppings
Input: A set of toppings \(T\), a budget \(b\in\mathbb{N}\), and a cost function for toppings \(c: T\rightarrow \mathbb{N}\)
Output: A selection of toppings \(T'\subseteq T\) such that \(\sum_{t \in T}c(t) \leq b\).

In this section you will learn how to read (and write) simulation code that implements a formal theory and is easy to understand that it does what we say it does. Take a look at the following code implementing Pizza Toppings . (We assume toppings are represented by Strings.)

def pizzaToppings(toppings: Set[String], budget: Int, cost: String => Int): Set[String] = {
  // Helper function to compute the cost of a subset of toppings.
  def subsetCost(subset: Set[String]): Int = {
    subset.map(cost) // Transform each element in subset to its cost using the cost function.
          .sum       // Sum all costs.
  }

  // Helper function to check if a given subset fits within the budget.
  def subsetWithinBudget(subset: Set[String]): Boolean = {
    subsetCost(subset) <= budget
  }

  powerset(toppings)            // The set of all possible subsets.
    .filter(subsetWithinBudget) // Filter (keep) all subsets that fit the budget.
    .random                     // Get a random subset.
    .getOrElse(Set.empty)       // If to random subset exists, return the emtpy set.
}

This code example combines several expressions we covered earlier, but by breaking down the components it is relatively easy to show that the code implements Pizza Toppings . Of course, even though math is much less ambiguous as verbal theory, that does not exclude there being multiple (equivalent) expressions in math. Some might be easier to translate to Scala code than others. We will encounter examples where rewriting the mathematical expressions of the formal theory can help in clarifying the relationship with the simulation.

In the next section, we explore how mathlib allows writing code for formal theories using set theory. The library also contains support for probability and graph theory. You can find out more at the library Github page, but using these advanced mathematics requires installing a local development environment such as Jupyter/Almond or Intellij (see Installing Scala and mathlib).

Set theory

In this book, set theory plays an important role in formalizing verbal theories, so we start exploring mathlib there. This section follows the same structure as Chapter 3 - Math concepts and notation.

Creating a set is similar to creating other collections such as lists. Set membership in Scala is an expression that evaluates to true if the element is within the set or false if it is not. The Scala code below creates the set \(P = \{\text{Ramiro},\text{Brenda},\text{Molly}\}\), then tests if \(\text{Ramiro}\in P\) and if \(\text{Saki}\in P\).

val p: Set[String] = Set("Ramiro", "Brenda", "Molly")

println("Ramiro" in p)
println("Saki" in p)

Subset and superset expressions also evaluate to true or false.

val animals = Set("cat", "cuttlefish", "turtle", "blue whale")
val mammals = Set("cat", "blue whale")
val thingsOnEarth = Set("cat", "cuttlefish", "turtle", "blue whale", "university", "chair")

println(mammals < animals)        // Mammals is a subset of animals.
println(thingsOnEarth > animals)  // Things on Earth is a superset of animals.

Intersection, union and difference evaluate to the correct super- or subset.

val yourFriends = Set("John", "Roberto", "Holly", "Doris", "Charlene")
val myFriends =  Set("Vicky", "Charlene", "Ramiro", "Johnnie", "Roberto")

println(yourFriends /\ myFriends) // Common friends using intersection.
println(yourFriends \/ myFriends) // Friends we know together using union.
println(myFriends \ yourFriends)  // Who I know that you don't, using difference.

Before we look at set builder notation, consider the following. In formal theories, we often use set theoretic notation not as Boolean tests, but to stipulate the output. E.g., in Pizza Toppings , the output is a subset \(T'\subseteq T\) that fits in budget. This notation, however, does not translate directly into Scala code. Consider the following rewrite of the formal theory output:

\(T' \in \mathcal{P}(T)\) such that \(\sum_{t\in T'}c(t)\leq b\)

This notation still does not easily translate, for two reasons. Firstly, because the relationship between \(T'\) and the desired property (fits in budget) is expressed in natural language. Secondly, because there are multiple outputs possible. Since the theory does not explicitly state which from the possible outputs is preferred, we need to assume it either returns the entire set or selects at random.

We can address the first issues by expressing the set of possible outputs and the relationship between \(T'\) and budget-fit explicitly in math using set building notation. The practice of decomposing parts of a theory (or code) into sub-parts is well known in mathematics and programming. It can help make math and code more readable, because we can abstract from the inner workings of a function. We make our lives easier by defining a help function for cost of subsets: \(cost: \mathcal{P}(T) \rightarrow \mathbb{N}\), where \(cost(T')=\sum_{t\in T'}c(t)\). Then the set of all subsets within budget can be defined as:

\[\left\{T'~\middle|~\mathcal{P}(T) \wedge cost(T')\leq b\right\}\]

Here, \(\mathcal{P}\) is the powerset notation. It denotes the set consisting of all possible subsets and \(T'\) is an element in that set. The set-builder notation describes the set of all possible subsets that satisfy the budget constraint. Dealing second issue (multiple possible outputs), we make the decision to return a random valid output.

def pizzaToppings(toppings: Set[String], budget: Int, cost: String => Int): Set[String] = {

The helper function that computes the cost of a subset is implemented as:

  // Helper function to compute the cost of a subset of toppings.
  def subsetCost(subset: Set[String]): Int = {
    sum(subset, cost)
  }

We add one more helper function to check if a subset fits within the budget.

  // Helper function to check if a given subset fits within the budget.
  def subsetWithinBudget(subset: Set[String]): Boolean = {
    subsetCost(subset) <= budget
  }

Finally, we can define the output of Pizza Toppings using set builder notation:

  { powerset(toppings) | subsetWithinBudget _ }.random.getOrElse(Set.empty)
}

You may have noticed this code is different from the example given at the start of this section. Similarly to multiple mathematical expressions being equivalent, there exist (many) different ways of implementing the same function. This is a example implementation uses set builder to construct a set of subsets that fit within budget. Set builder, in essence, filters out all elements (in the case above the elements are sets) from the given set that do not satisfy the evaluation function on the right side.

{ givenSet | evalFun _ }
Question 9.6

Complete the code scaffold below such that the set builder expression evaluates to the subset of odd numbers in numbers.

// Function that checks if a number is odd.
def isOdd(n: Int): Boolean = {
  ???
}

val numbers: Set[Int] = (0 to 11).toSet   // Set consisting of numbers 0 to 11

{ ___ | ___ }

The final expression for working with sets is the cardinal product \(\times\). The cardinal product between two sets \(A\) and \(B\) returns a set with all possible pairs of elements in \(A\) and \(B\). Pairs, in Scala, are represented as tuples.

val pair: (Int, String) = (4, "Book")

The type of a tuple combines the types of the elements. The cardinal product between two sets yields a set with the subtype of the tuple.

val numbers: Set[Int]  = Set(1, 2, 3, 4)
val items: Set[String] = Set("Book", "Candle", "Wine")

val itemNumberPairs: Set[(String, Int)] = items x numbers

println(itemNumberPairs)

Simulation architecture

Now that we’ve covered the basics for Scala and mathlib we can go cover what constitutes a computer simulation of a formal theory. To run a computer simulation you minimally need two components: The implementation of the formal theory and sample input (Guest & Martin, 2020). The advantage of simulations are that you can let the computer do the hard work computing many input-output mappings, but it is still a lot of work if have to supply each input manually.

A simulation architecture consists of the implementation of the formal theory and an input generator that supplies inputs.

Manually supplying input can still be useful for checking for errors in the code, or to investigate particular limit cases of the theory and initial explorations. We will use manual inputs throughout the next chapters.

However, a more complete simulation covers many inputs, preferably exploring a wide range of different cases. For example, with the Pizza Toppings example one might want to explore inputs ranging from few to many toppings, with many different cost functions and budget. In these cases it is useful to write a helper function that can generate input with various properties.

Unfortunately, like with formalizing verbal theory, there is not a single recipe for writing input generators. Generators are highly contingent on the theory and the parts of the input domain you wish to simulate. Within the context of this book, you don’t need to write your own generators, but over the next chapters you will use example input generators provided by us.

We can put most generators in one of two categories: unconstrained input generation or constrained input generation. Understanding the difference between the to types of generators is important, as it impacts the kinds of inputs one can generate, which in turn will impact the computer simulation results.

Any procedure for randomly generating input will likely be biased. That means that some inputs are more likely to be generated than others. This can be due to particular structure in the input space, or due to properties of the generator.

A simplified representation of this bias is shown in the figure below. Out of the set of all possible inputs, the darker areas might be more likely to be generated than the light. Of course, input bias will also bias the output of the simulations.

Bias in an unconstrained random generator may not cover all possible inputs.

We can deal with this bias by analyzing how well the generated inputs cover the parts of the space you are interested in. An unconstrained input generator is best used as such. These generators have bias over which we have no control such as in the figure above. Bias doesn’t render the simulations useless, as long as we can characterize the bias and have some understanding of how it impacts the results and limits our conclusions.

Alternatively, we can try to implement a constrained input generator. These types of generators are still biased, but we have some degree of control over the inputs they generate. Since there is still bias, it is wise to take that into account, but we may be better able to cover the input space by setting the right constraints. We can even use multiple sets of constraints to cover more area of the input space, as is shown in the figure below.

Bias in constrained input generators. Each “bubble” is a particular set of constraints on the same input generator, moving the bias across all possible inputs. By combining these biases, we can cover more of the input domain.

So why don’t we always write constrained input generators? This is because they are hard to make, at least harder than the unconstrained versions. It is a question we need to ask when developing simulations: Does the bias in the unconstrained generator skew the results too much? Then consider investing in developing a constrained input generator. For now, you can lean back and enjoy the generators we provide.

Cheat sheet


/**** BASIC TYPES ****/

val integer: Int = 3
val double: Double = 2.7
val bool: Boolean = true
val character: Char = 'c'
val string: String = "Awesome"

val x: Int = integer
val y: Double = double

println(x + y)
println(x - y)
println(x * 3)
println(y / 2)            // Division.
println(y % 2)            // Remainder or modulo.
println(Math.pow(x, y))   // Exponentiation, x^y.
println(Math.min(x, y))
println(Math.max(x, y))

println(true && true)   // Logical and.
println(true || false)  // Logical or.
println(true ^ true)    // Logical xor.
println(!true)          // Negation.

/**** Lists ****/
val forecastThisWeek = List(22.2, 23.1, 23.7, 22.3, 24.3, 24.7, 25.1)
val forecastNextWeek = List(22.3, 19.8, 18.4, 18.0, 17.6, 17.5, 17.2)

println(23.1 :: forecastThisWeek)               // Prepend element.
println(forecastThisWeek ::: forecastNextWeek)  // Prepend list.
println(forecastThisWeek.size)                  // Number of elements in list.
println(forecastThisWeek.contains(23.7))        // Does the list contain element?
println(forecastThisWeek.head)                  // The first element of the list.
println(forecastThisWeek.tail)                  // Everything except the first element.
println(forecastThisWeek(3))                    // The n-th element.
println(forecastThisWeek.isEmpty)               // Checks whether the list is emtpy.

val list = List(1, 2, 3, 4)
def sq(x: Int): Int = x * x   // Function that computes the square root of x.
println(list.map(sq))         // Map each value in list to sq(_)

def isEven(x: Int): Boolean = x % 2 == 0  // Function that checks whether x is even.

println(list.exists(isEven))    // Does list contain an element that isEven?
println(list.exists(_ > 3))     // Implicit function, does the list contain an element larger than 3?
println(list.forall(isEven))    // Do all elements in list return true for isEven?
println(list.forall(_ <= 100))  // Implicit function, does the list contain an element larger than 3?
println(list.filter(isEven))    // Filter out all elements that return true for isEven.
println(list.filter(_ < 3))     // Filter out all elements less than 3.
println(list.sum)               // The sum of all numbers in list.
println(list.product)           // The product of all numbers in list.

/**** Tuples ****/
val menuItem: (String, Double) = ("Vegan pie", 9.90)
println(menuItem._1)            // Get the first value of the tuple.
println(menuItem._2)            // Get the second value of the tuple.

/**** Sets ****/
val animals = Set("cat", "cuttlefish", "turtle", "blue whale")
val mammals = Set("cat", "blue whale")
val thingsOnEarth = Set("cat", "cuttlefish", "turtle", "blue whale", "university", "chair")

println("cat" in animals)         // Test if element is in  the set.
println(mammals < animals)        // Mammals is a subset of animals.
println(thingsOnEarth > animals)  // Things on Earth is a superset of animals.

val yourFriends = Set("John", "Roberto", "Holly", "Doris", "Charlene")
val myFriends =  Set("Vicky", "Charlene", "Ramiro", "Johnnie", "Roberto")

println(yourFriends /\ myFriends) // Common friends using intersection.
println(yourFriends \/ myFriends) // Friends we know together using union.
println(myFriends \ yourFriends)  // Who I know that you don't, using difference.

println({ Set(0, 1, 2, 3, 4, 5) | isEven _ }) // Build a set of even numbers.

println(myFriends x animals)      // Set of all pairs of friends and animals using cardinal product.

References

Guest, O., & Martin, A. E. (2021). How computational modeling can force theory building in psychological science. Perspectives on Psychological Science 16(4),789-802.

Odersky, M., Spoon, L., & Venners, B. (2019). Programming in Scala, 4th Edition. Artima.

Scala and mathlib - October 4, 2021 - Mark Blokpoel and Iris van Rooij