Terse Scala notes

(periodically revisited and updated, most recently August 2022)
And in 2021 I have started the Terse Scala 3 notes.

Notes from the 2014 ScalaTutorial.pdf, http://www.scala-lang.org/docu/files/ScalaTutorial.pdf

Also from 2014 Scala by example, http://www.scala-lang.org/docu/files/ScalaByExample.pdf

Nothing below is my own work, apart from the extraction of the details, and an attempt to create a set of terse notes to jog my memory and provide a reference page on basic Scala.

REPL
Read-Evaluate-Print-Loop, use :paste to enter multi line

Syntactic Sugar
Anytime in Scala they thought of short cuts to syntax to make the code smaller they call it syntactic suger.

No static keyword
object keyword to declare singleton objects holding fields and methods with only a single instance. Objects are created the first time one of its members is accessed - lazy evaluation.

Compiler and run
scalac to compile, and scala -classpath to execute.

javap -c -private ClassNameWithoutDotClass  // lets you see byte code of a class file
scalac -print  // gives you some idea of what its going to do during compiling

* is a valid scala identifier (eg method name)

import java.text.DateFormat._

No condition expression … ? … : …
You can use the Scala if .. else … to choose between two expressions

def abs(x: Double) = if (x >= 0) x else x

I found if I am building up big debug strings etc I now just use multiline strings with embedded expressions

def showDetailsForSomeReason = {
   s"""
      |${if (malazanVal.isDefined) "Caladan Brood" else "Anomanda Rake"}
      |Some fancy words about the Empress
    """.stripMargin
}

No semi-colon required at line end
You can use the semi-colon at the end of a line, but you do not have to, a semi-colon is inserted implicitely unless one the following applies: You cannot end a line with a period or an infix-operator. Also, the next line cannot start with a word which cannot start an expression. Finally if we are inside parenthesis (..) or brackets that cannot contain multiple statements.

Identifiers
Start with a letter or _ and followed by letters and or symbols or .
OR can start with an operator or _ and be followed by 0 or more operators and _
eg x, Room10a, +, –, fold1
:, +_vector
so, x+-y is parsed as x, +-, y

Reserved words

abstract, case, catch, class, def, do, else, extends, false, final,
finally, for, forSome, if, implicit, import, lazy, macro, match, new, null, object, override,
package, private, protected, return, sealed, super, this, throw, trait,
try, true, type, val, var, while, with, yield,
_, :, =, =>, <-, <:, <%, >:, #, @

while and do while
These are in the language for convenience. Being imperative constructs they could have been left out and implemented using functions.

No break, continue, jump
Not in scala! No for-loops in the java sense, but there are for comprehensions.

scala.AnyRef
This is the base class of everything, and this is implicit (no need to put this in your code)

override
You can override any method in a base class, but you must use the keyword override. If its abstract there is no need for override. Scala allows overriding in a contra/covarient fashion.

class Complex(real: Double, imaginary: Double) {
  def re = real
  def im = imaginary
  override def toString() =
    "" + re + (if (im < 0) "" else "+") + im + "i"
}

Its really worth reading up on constructor args defined with or without val or var, and what override means. For instance a constructor arg without val or var if only used in the constructor will not be made into a class field. But if it is used in another function then it will be. If you have a sub-class which also defines it as a val then it has to have override - read up on it!

Function definitions
Start with def

// def foo() {} is deprecated procedure format now, use def foo():Unit = {}
def sort(xs: Array[Int]) : Unit = {...}

Functions can be nested

// You can say :Unit for return type, or leave it off, just like def sort() {
// scala loves type inference - when it can work out the type let it.
def sort(xs: Array[Int]) = {
  def swap(i: Int, j: Int) {
    val t = xs(i); xs(i) = xs(j); xs(j) = t
  }
}

Notice that xs is accessible to the nested function.

Variable definitions
Start with var. ie mutable, can be reassigned. In scala, try not use use vars.

Value definitions
Start with val. ie immutable, cannot be reassigned. Readonly variables.

Declaration of type
Is done after the symbol and a colon, and if it can be inferred by the compiler it can be missed off.

def swap(i: Int, j: Int) : Unit =

Everything is an object
No primitives, so even numbers are objects

1 + 2 * 3 / x
(1).+(((2).*(3))./(x)) // line above is actually method calls

Standard type alias
Int has a type alias for int, double is a type alias for Double. ie you can use the Java primitive types but really you are using the Scala objects because of the type aliases. Having said that, Int is usually converted to a Java primitive int and Boolean to boolean etc for efficiency, and converted back as needed.

Lexer uses longest match rule for tokens
(1).+(2) is needed, because 1.+(2) would result in lexer thinking 1. was a double value of 1.0. So instead write it as (1).+(2). In reality just have spaces rather than braces.

Unit is like void in C/C++

def oncePerSecond(callback: () => Unit) = {
  while (true) { callback(); Thread sleep 1000 }
}

ie unit means nothing is returned by a function.

Array types

def xs: Array[Int]

Array types are written Array[T], and array selections are writtern a(1).

Default values

private var contents: T = _

This assigns the default value to contents. The default for numeric is 0, false for Boolean, () for Unit and null for all oject types.

In Scala, every defined variable has to be initialized at the point of its definition. There are lots of horrid mind games where you define a superclass with uninitialized fields and then override them in sub-classes but assign value and lazy init are all made part of the (interview) question.

Predicate function
Simply a function which returns true or false - so you can filter on it etc.

Functions are objects
You can pass them as parameters, store them in variables, and return them from other functions.

object Timer {
  def oncePerSecond(callback: () => Unit) = {
    while (true) { callback(); Thread sleep 1000 }
  }
  def timeFlies() = {
    println("time flies like an arrow...")
  }
  def main(args: Array[String]) = {
    oncePerSecond(timeFlies)
  }
}

println
Rather than System.out.println

=>
Seperates a functions arguments from its body. For example a function parameter as below:

f: Int => Int

Anonymous functions
No need to name a function if it is only used once.

object TimerAnonymous {
  def oncePerSecond(callback: () => Unit) = {
    while (true) { callback(); Thread sleep 1000 }
  }
  def main(args: Array[String]) = {
    oncePerSecond(() =>
      println("time flies like an arrow..."))
  }
}

Default parameter values

def decorate(str: String, left: String = "[", right: String="]") = left+str+right

When calling a function you can name the parameters, and change the order

decorate( right=">", left="<", str="Angles")

Variable number of args

def sum(args: Int*) = {
 var result =0
 for (arg <- args) result += arg
 result
}
def caitaliseAll(args: String*) = {
 args.map { arg => arg.capilatize }
}

Convert a generator into a sequence of arguments _*

val s = sum(1 to 5: _*)

or for recursive with variable arguments

def recursiveSum(args: Int*) : Int = {
  if (args.length ==0) 0
  else args.head + recursiveSum(args.tail : _*)
}

Currying
An anonymous function implementation is:

def sum(f: Int => Int, a: Int, b: Int): Int =
  if (a > b) 0 else f(a) + sum(f, a + 1, b)

def sumInts(a: Int, b: Int): Int = sum(x => x, a, b)
def sumSquares(a: Int, b: Int): Int = sum(x => x * x, a, b)

We define two functions to call sum, and they both pass in anonymous functions to sum and it calls them to do its job.
The function is not really interesting to sum though, so we can seperate it out to be a partially applied parameter, and define the operations sumInts and sumSqaures to only define the function part, allowing this:

def sum(f: Int => Int):(Int, Int) => Int = {
  def sumF(a: Int, b: Int): Int =
    if (a > b) 0 else f(a) + sumF(a + 1, b)
  sumF
}
def sumInts = sum(x => x)
def sumSquares = sum(x => x * x)
val sumIntsAlt = sum(x => x)        // Given to show you could use val rather than def
val sumSquaresAlt = sum(x => x * x) // Given to show you could use val rather than def

By defining sum as having two sets of parameters we can define partially resolved calls to it. So now calling these read very well:

sumSquares(1, 10)

This is so useful we have some Syntactic Sugar to make it need less characters:

def sum(f: Int => Int)(a: Int, b: Int): Int =
  if (a > b) 0 else f(a) + sum(f)(a + 1, b)

First class function
Scala supports first-class functions, which means you can express functions in function literal syntax, and that functions can be represented by objects, which are called function values.

(x: Int) => x + 1    // this is function literal syntax

function literal
A function with no name in Scala source code, specified with function literal syntax. Think if it like a string, println(“abc”), we didn’t need to have val s = “abc”;println(s) in that statement, and scala allows this for functions as well, calling this ability a ‘function literal’.

(x: Int, y: Int) => x + y

Anonymous functions in source code are called function literals. At run time, function literals are instantiated into objects called function values.

Higher order functions
Functions which take other functions as parameters are higher order functions.

def filter(p: T => Boolean): Array[T]

Filter is a higher order function which takes a parameter of type T returning a Boolean (remember thats called a predicate, because it returns true or false). The filter function itself returns an array of type T - ie its using parameterized type.

Procedures
If a function is of type Unit, it is known as a procedure.

Call by value
This is the default in Scala. Arguments are evaluated and the result passed into the function call.

Call by name
Precede the parameter type with =>
Call by name means the argument is only evaluated when it is used.

Infix syntax
If there is only one parameter there is no need for (), so

df format now
df.format(now) // the same as the above line

No need for parenthesis
If you declare a method without arguments then you don’t need to use them when calling the function

class Complex(real: Double, imaginary: Double) {
  def re = real
  def im = imaginary
}

The difference between a def and a val is that the def is evaluated each time it is called whereas a val field is evaluated when the class is created.

Operators ending :
In Scala an operator ending with : is treated as a method of its right operand.

 x :: y = y.::(x)    whereas  x + y = x.+(y) 

But, operands of a binary operation are evaluated left to right, so if D and E are expressions with possible side effects then D :: E is translated to {val x = D; E.::(x)}
Also, operators ending : are right associative, whereas others are left associative.

x :: y :: z = x :: (y :: z) whereas x + y + z = (x + y) + z

Imperative vs functional
Loops in imperative programs can always be modeled by recursion in functional programs. Lifted from the Scala glossary.

Imperative programming
The imperative style of programming emphasizes careful sequencing of operations so that their effects happen in the right order. The style is characterized by iteration with loops, mutating data in place, and methods with side effects. It is the dominant paradigm of languages such as C, C++, C# and Java, and contrasts with the functional style.

Declarative programming
Expresses the logic of a computation without describing its control flow – eg SQL, regexp, functional programming

Functional programming
The functional style of programming emphasizes functions and evaluation results and deemphasizes the order in which operations occur. The style is characterized by passing function values into looping methods, immutable data, methods with no side effects.

Constructors
Have to have a primary constructor. Every auxilary constructor must start with either a call to another auxilary or to the primary. If you don’t define a primary constructor then it is there anyway, with no params.

Primary constructor
The parameters are given after the class definition:

class Person(val name: String, val age: Int) {
  println("Just constructed");
}

Params of the primary constructor turn into fields that are initialised with the construction parameters

You can elimitate many auxilary constructors using default arguments.

class Person(val name: String = "", private var age: Int =0) {
}

You can make the parameter a field - ie private by not using val or var, same as private[this]val. If it has no val or var and is not used in any methods etc then the compiler will optimise it away. ie just params in the ctor.

class Person(name: String = "", age: Int =0) {
}

Private primary constructor
Only callable from auxilary constructors:

class Person private(val id: Int) {
}

Inner classes
They are owned by each instance, not like in Java where they are owned by the outer class.

So, if you create multipl instances of the class, each in a different instance then they are not of the same type - they are part of their owning instance.

BUT, if you want them to be compatible you can use type projection. This would for example mean “a Member of any Network”

class Network (
  class Member(val name: String) {
    val contacts=new ArrayBuffer[Network#Member]
  }
  ...
}

Ref to outer class
As in java Network.this. Or, you can alias it

class Network(val name: String) {
  outer =>
    class Member(val name:String) {
      println(outer.name);
      println(Network.this.name);
    }
}

Companion objects
Don’t have static methods inside the class, stick them in the object of the same name.

Default Object inherit from class

object Foo extends bar("Do nothing") {
}

In effect you are using the singleton to define a global object which can be reused everywhere.

Apply methods
Define these in the companion object, and use them without needing the new keyword.

object Accounts{
     def apply(initialBalance: Double) = new Account(new UniqueNumber(), initialBalance)
}
//Usage
val acct = Account(10000.0)

Main method
Use the App trait and you don’t need a main method

object Hello extends App {
  // args are available as the args property of App trait
  println("Hello World")
}

Verbose version

object Hello {
  def main(args: Array[String]) {
    println("Hello World")
  }
}

Exceptions
Pretty similar to java, except you catch them and handle them with case

case class TypeError(s: String) extends Exception(s) {} // example of my own exception

throw new TypeError("cannot unify " + s(t) + " with " + s(u))   // example of throwing one

try {
    // something
} catch {
    case TypeError(msg) => msg
}

Tail Recursion
Imagine a function where the last thing you want to do is call back to the start of the function again, like a loop. Effectively you are going to replace the parameters with new values for them, and call the function again. In scala this is optimised to use the same stack frame - so its recusive, but has no impact on stack size.
In general all ‘tail-calls’ could be optimised like this, ie when a function calls another function as its last action. But the JVM lacks the optimisations for this, so in Scala only recursive calls to the same function, where the last expression in the function is a call to itself, are optimised in this way.

def gcd(a: Int, b: Int): Int = if (b == 0) a else gcd(b, a % b) // is tail recursive
def factorial(n: Int): Int = if (n == 0) 1 else n * factorial(n - 1) // is NOT as the multiplication is the last expression

Identifiers and operators not distinguished in Scala.
Identifier can be sequence of letters and digits starting with a letter, or a sequence of special characters, such as “+”, “*” or “:”. Any identifier can be used as an infix operator in Scala - ie if it takes 1 param rater than do call(me) you can say call me - this changes in scala 3…

Classes can have parameters
Below the real and imaginary values must be passed in when creating a complex. re() and im() infer the return type.

class Complex(real: Double, imaginary: Double) {
  def re() = real
  def im() = imaginary
}

Inference
Scala will infer types whenever it can, so you do not have to declare return types and so on if it is obvious to the compiler.

Getters Setters

// Scala synthesises getters and setters, foo (getter) and foo_ (setter)
var foo

// Scala only does getter
val foo

// provide a mutator, so no getters and setters, just your own mutator
private var value=0

/**
 * Scala code can use = or leave it off (if its Unit type).  
 * Between scala versions the style guide changed and the inventor has regrets...
 * more importantly for you, they may change Scala in a future release and just
 * drop some features
 */
class Silly(var value:Int) {
  def increment1() { value += 1 }   // unit type can ignore the =, bad practice
  def increment1() = { value += 1 }
  def increment2() = value += 1     // If you work at twitter you like this
}

// No braces needed in the definition, don't need braces to call it either
def current = value

// Object-private. Accessing someObject.value is not allowed
private[this] var value = 0

Case classes
Are really excellent - I love them enough to give up Java - Java has records now, but scala is still better!

abstract class Tree
case class Sum(l: Tree, r: Tree) extends Tree
case class Var(n: String) extends Tree
case class Const(v: Int) extends Tree

Case Classes: new not needed
Case classes can be created without new keyword

Case Classes: Getter functions
Automatically created for all contructor args

Case Classes: Default implementations
equals, hashcode based on the structure of the class not its identity. toString is generated.

Case Classes: decomponsable by pattern matching
Case classes can be used in pattern matching - pattern matching is a big deal.

Alias for types
In scala you can use ‘type’ to name a type. For instance a function which takes a string and returns an int would be of type String => Int. So you can do:

type Environment = String => Int

Pattern matching
Don’t forget, case classes are great for pattern matching.

def eval(t: Tree, env: Environment): Int = t match {
  case Sum(l, r) => eval(l, env) + eval(r, env)
  case Var(n) => env(n)
  case Const(v) => v
}

The alias type Environment means a function taking a String and returning an Int. eval is a function which takes a Tree, and a type Environment. It then matches the tree (ie t match {…) to see if it is a case class Sum or case class Var or case class Const. Depending on the match that works it returns a different Int. If none match it throws an exception.

The match method takes as arguments a number of cases which match patterns. Patterns are generally built from case classes, pattern variables, the wildcard _ and constant identifiers (start with upper case letters). Each variable may only occur once in the pattern - eg Sum(x, x) is illegal.

Apply Unapply
An apply() method means you can create a the class without using new.
unapply() means you can use the class in pattern matching

Pattern Matching syntactic sugar

case Some(e1) => e1 ; case None => "Foo"

This is pattern matching anonymous functions. ie case expressions without a match. It is equivalent to:

(x=> x match { case Some(e1) => e1 ; case None => "Foo" })

No need for the return keyword
The last expression will be automatically returned. Return keyword is considered an anti-pattern.

Pattern matching wild card and guard

def derive(t: Tree, v: String): Tree = t match {
  case Sum(l, r) => Sum(derive(l, v), derive(r, v))
  case Var(n) if (v == n) => Const(1)
  case _ => Const(0)
}

There is a guard in the case expression ‘case Var(n) if (v == n)’. ‘case _’ means match anything remaining.

Abstract class
Abstract classes may have deferred members which are declared but have no implementation. They cannot be created using new.

Traits

Like java interfaces which can also contain code.

trait Ord {
  def < (that: Any): Boolean
  def <=(that: Any): Boolean = (this < that) || (this == that)
  def > (that: Any): Boolean = !(this <= that)
  def >=(that: Any): Boolean = !(this < that)
}
class Date(y: Int, m: Int, d: Int) extends Ord {
  def year = y
  def month = m
  def day = d
  override def toString(): String = year + "" + month + "" + day
  override def equals(that: Any): Boolean =
    that.isInstanceOf[Date] && {
    val o = that.asInstanceOf[Date]
    o.day == day && o.month == month && o.year == year
  }
  def <(that: Any): Boolean = {
    if (!that.isInstanceOf[Date])
      error("cannot compare " + that + " and a Date")
    val o = that.asInstanceOf[Date]
    (year < o.year) ||
      (year == o.year && (month < o.month ||
      (month == o.month && day < o.day)))
  }
}

Sealed
A sealed trait or class means no subclasses or data constructors which extend this can be formed outside the file in which the type is defined. Commonly used in case classes to seal the trait - forget scala 2 enumerations, use sealed case classes with trait. Scala 3 has enums, reason enough to move on.

sealed trait Validity
case object Valid extends Validity
case object Invalid extends Validity

isInstanceOf
This is like Java instanceOf and returns true only if its of a given type. Bad to use isInstanceOf in your code because pattern matching is better.

asInstanceOf
This is like Java cast, if not of given type throws ClassCastException. Bad to use asInstanceOf in your code.

Generics, Parameterised types, code parametrized by types

class Reference[T] {
  private var contents: T = _
  def set(value: T) { contents = value }
  def get: T = contents
}
object IntegerReference {
  def main(args: Array[String]) {
    val cell = new Reference[Int]
    cell.set(13)
    println("Reference contains the half of " + (cell.get * 2))
  }
}

‘T’ is a type parameter of class Reference and its subclasses.

Parameterised methods
You can have a generic methods as below.

def isPrefix[A](p: Stack[A], s: Stack[A]): Boolean = {
  p.isEmpty ||
  p.top == s.top && isPrefix[A](p.pop, s.pop)
}
println(isPrefix[String](s1, s2))

Because Scala has a good type inferencer, you could instead call this and miss out [String]

println(isPrefix(s1, s2))

Type parameter upper bounds <:
If a method only wanted to work for classes which implement the Ordered trait, you can bound the parameter as below

trait Set[A <: Ordered[A]] {
  def incl(x: A): Set[A]
  def contains(x: A): Boolean
}

Type parameter A must now be a subtype of Ordered[A]

View bounds <%

trait Set[A <% Ordered[A]] ...
class EmptySet[A <% Ordered[A]] ...
class NonEmptySet[A <% Ordered[A]] ...

These are weaker than plain bounds <:
<% specifies that the bounded type must be convertible to the bound type T using implicit conversion.

Type parameter lower bounds >:
Retrict a type parameter to range over supertypes.

class Stack[+A] {
  def push[B >: A](x: B): Stack[B] = new NonEmptyStack(x, this)

+A is a covarient type, see below. Function push only accepts supertypes of A.

Generic types by default have non-varient subtyping
So if you define a parameterised stack and instantiate a Stack[String] and a Stack[Any] there is no sub-type relationship between them, even though Any is a subtype of String.

Co-varient subtyping
Putting a plus sign (+) before the type parameter. The class or trait then subtypes covariantly with—in the same direction as—the type annotated parameter. For example, List is covariant in its type parameter, so List[String] is a subtype of List[Any].

Contravarient subtyping
Putting a minus sign (-) before the type parameter. The class or trait then subtypes contravariantly with—in the opposite direction as—the type annotated parameter. For example, Function1 is contravariant in its first type parameter, and so Function1[Any, Any] is a subtype ofFunction1[String, Any].

Least Types - Nothing
You cannot parameterise objects with types. Nothing contains no value and is the bottom type.

object EmptyStack extends Stack[Nothing] { ... }

This idiom lets you have a single value denoting empty stack of arbitrary type.

Stack example.

abstract class Stack[+A] {
  def push[B >: A](x: B): Stack[B] = new NonEmptyStack(x, this)
  def isEmpty: Boolean
  def top: A
  def pop: Stack[A]
}
object EmptyStack extends Stack[Nothing] {
  def isEmpty = true
  def top = error("EmptyStack.top")
  def pop = error("EmptyStack.pop")
}
class NonEmptyStack[+A](elem: A, rest: Stack[A]) extends Stack[A] {
  def isEmpty = false
  def top = elem
  def pop = rest
}

Option & Some
Any time you may get some value or a null, use an Option, which is a return type meaning it can be None or Some. ie never compare to null as you do in Java, always use Option. In Scala we use

Option( Person ) human = people.get("Jonathan")
if (human.isDefined) println("Jonathan is here")

Better to say

println( people.getOrElse("Jonathan", "no Jonathan, boo hoo"))

can match case statements with None

Map

 val scores=Map("Alice" -> 10, "Bob" ->3, "Cindy" ->8)  // creates immutable Map[String, Int]
 val scores=Map(("Alice" , 10), ("Bob", 3), ("Cindy", 8)) // not prefered, they prefer >

 // Mutable version
 val scores=new scala.collection.mutable.HashMap[String, Int]

 val bobsScore = scores.getOrElse("Bob", 0)    // This is a short cut to the line below
 val bobsScore = if (scores.contains("Bob")) scores("Bob") else 0 // or you could
 val bobsScore = if (scores.get("Bob").isDefined) scales("Bob") else 0

 // get returns an Option which is either Some(value for key) or None

 // assignment is done with left brackets
 scores("Bob") = 10
 scores("Fred") = 7

 // OR
 scores += ("Bob" -> 10, "Fred" -> 7)
 scores = scores - "Alice"   // removes an element

 scores.keySet
 scores.values
 for ((key, value) <- scores) print (key+":"+value)

 // To reverse keys and values try

 for ((key, value) <- scores) yield (value, key)

 scala.collection.immutable.SortedMap
 scala.collection.mutable.LinkedHashMap

Tuples
Predefined container case classes for any types - so if you want to return a pair of values use a Tuple2.

def divmod(x: Int, y: Int) = new Tuple2[Int, Int](x / y, x % y)

As ever, scala like to provide special syntactic sugar for useful things like tuples, so the shorthand is:

def divmod(x: Int, y: Int): (Int, Int) = (x / y, x % y)

Access to members is _1, _2 etc.

val xy = divmod(x, y)
println("quotient: " + xy._1 + ", rest: " + xy._2)

divmod(x, y) match {
case (n, d) => println("quotient: " + n + ", rest: " + d)  // scala infers (n,d) is a Tuple2.
}

You can use pattern matching to extract values from a pair, and tuples are case classes containing 2 or 3 or 4 or…. number of values. eg

val (label, value) = pair

could be written as

val label = pair._1
val value = pair._2

So, within the tuple the values are stored in fields called _1, _2 etc.

Arrays
Use Array if length is fixed, and ArrayBuffer if it varies.

val nums=new Array[Int](10)  // array of 10 integers all 0.
val s = Array("Hello", "World") // no need of new, length 2 etc
s(0) = "Goodbyte"  // use () to access members, not []

ArrayBuffers are variable length.

 import scala.collection.mutable.ArrayBuffer
 val b=ArrayBuffer[Int]()
 b += 1
 b += (1,2,3,4)
 b ++= Array(8,13,21) // ++= means append a collection
 b.trimEnd(5)


 b.insert(2, 6) // 2 is the insert posion, 6 is the data to insert.
 b.insert(2, 6,7,8)
 b.remove(2)
 b.remove(2,3) // from pos 2 remove the next 3, starting with 0 pos...
 b.toArray

 for (i <- 0 until (a.length, 2)) // 0 to length, every 2nd one.

 (0 until a.length).reverse // reverse a string

 for (elem <- a) // dont need the index, just the element itself

 val result = for (elem <- a) yield 2 * elem // comprehension yields a collection
 // (array gives an array, arraybuffer gives an arraybuffer)

Multi-Dimensional

val matrix = Array.ofDim[Double](3,4) // 3 rows 4 cols
matrix(row)(column) = 4 // ie ()()

Sum, Sort
.sum, .max are all build in.

try this:

val b = ArrayBuffer(1,7,2,9)
val bSorted = b.sorted( _ < _)

Ordered is a trait that allows sorting.

scala.util.Sorting.quickSort(a)

Lists

val fruit: List[String] = List("apples", "oranges", "pears")
val nums : List[Int] = List(1, 2, 3, 4)
val diag3: List[List[Int]] = List(List(1, 0, 0), List(0, 1, 0), List(0, 0, 1))
val empty: List[Int] = List()

Nil represents an empty list. Infix operator :: (pronounced cons) means the rest of the list. ie x :: xs means first element x and following items xs. So we could instead have

val fruit = "apples" :: ("oranges" :: ("pears" :: Nil))
val nums = 1 :: (2 :: (3 :: (4 :: Nil)))
val diag3 = (1 :: (0 :: (0 :: Nil))) ::
(0 :: (1 :: (0 :: Nil))) ::
(0 :: (0 :: (1 :: Nil))) :: Nil
val empty = Nil

:: cons
:: means the rest of the list, it is right associative. So the two lines below are the same - no need for ()

val nums = 1 :: (2 :: (3 :: (4 :: Nil)))
val nums = 1 :: 2 :: 3 :: 4 :: Nil

cons is defined as a case class in Scala’s standard lib, so can also be used in pattern matching.

match {
  case x :: xs1 => insert(x, isort(xs1))
}

head, tail, isEmpty
head is 1st element, tail is the rest, and isEmpty return true iff list is empty.

zip
Makes a new list of tuples holding the (1st elem list 1, 1st elem list 2),(2nd of list 1, 2nd of list 2) etc :::

Concatination

foldLeft
Call a function on every member, passing in an accumulator to hold the result.

reduceLeft
Only works on non-empty lists

def reduceLeft(op: (A, A) => A): A = this match {
  case Nil => error("Nil.reduceLeft")
  case x :: xs => (xs foldLeft x)(op)
}
def foldLeft[B](z: B)(op: (B, A) => B): B = this match {
  case Nil => z
  case x :: xs => (xs foldLeft op(z, x))(op)
}

Hang on, what? Looking at the List implementation of the sum function you see

def sum(xs: List[Int]) = (xs foldLeft 0) {(x, y) => x + y}

{(x, y) => x + y} is the function to add a value to another.
xs foldleft 0 means xs.foldleft(0) ie call foldleft with an accumulator value of 0. Then we look inside the foldleft definition and we see for a non empty list we are going to call xs.foldleft( op(current accumulator value, first list element)(op) ie this will return first element + (return from recursive call to add next element to next element to….)

foldRight and reduceRight
For right leaning trees.

def reduceRight(op: (A, A) => A): A = this match {
  case Nil => error("Nil.reduceRight")
  case x :: Nil => x
  case x :: xs => op(x, xs.reduceRight(op))
}
def foldRight[B](z: B)(op: (A, B) => B): B = this match {
  case Nil => z
  case x :: xs => op(x, (xs foldRight z)(op))
}

Symbolic abbreviations
/: is short for fold left, and \: is for fold right

def /:[B](z: B)(f: (B, A) => B): B = foldLeft(z)(f)
def :\[B](z: B)(f: (A, B) => B): B = foldRight(z)(f)

flatten
Given a list of lists, merge them into a single list. Actually a method of object List not class list.

def flatten[A](xs: List[List[A]]): List[A] =
  (xs :\ (Nil: List[A])) {(x, xs) => x ::: xs}

Map
Apply a function to each element and return the results as new elements which will probably be of a different type.

flatMap
Apply a function, and then if the elements returned are lists then flatten them to a single list.

Other operations
take, drop, foreach, forAll, exists, sum, sort, reverse

Sequence generator <-
To generate a sequence line 0<-10, or to use a list as the source of a sequence

For Comprehension
Notation to simplify the common patterns of higher order functions!

persons filter (p => p.age > 20) map (p => p.name)   // higher order functions
for (p <- persons if p.age > 20) yield p.name   // for comprehension!

For comprehension is syntactic sugar for composition of multiple operations with foreach, map, flatmap, filter or withFilter.

v1 and v2 below are equivalent. the for comprehension is short hand for nested flatmap etc calls. Why? Well, if you write a database layer and every function returns a Try[A], then you will see that Try.flatMap only processes on a success. So rather than having lots of calls, each one comparing for success, you can just chain them together and have a recover for errors.

You can read the ‘<-‘ as ‘in’.

  val v1 = for {
    i1 <- (1 to 3)
    _ = println(s"i1=$i1")
    j1 <- (4 to 6)
    t = j1*i1
    if (j1!=5)
  } yield (i1, j1, t)

  val v2= (1 to 3)
    .flatMap({ case i1 =>
      println(s"i1=$i1")
      (4 to 6)
        .withFilter({case j1=> j1!= 5})
        .map({ case j1 =>
          val t = j1 * i1
          (i1, j1, t)
        })
    })
  println(s"v1 = $v1")
  println(s"v2 = $v2")

Streams are deprecated for LazyList
Never ending sequences, trick is to pass by name so things do not get evaluated unless they are needed. Anyway, Scala had the Stream class, now it has the LazyList instead. You construct a LazyList using cons and empty.

val xs = LazyList.cons(1, LazyList.cons(2, LazyList.empty))

Rather than get into Scala LazyList you should try out fs2, which is even more fun.

Iterators
The imperative version of streams. No data structure within the iterators, they allow one to step through a sequence using next and hasNext. Next and hasNext are abstract methods. Iterators provide most of the other functions that Lists do.

val it: Iterator[Int] = Iterator.range(1, 100)
while (it.hasNext) {
  val x = it.next
  println(x * x)
}

Concrete iterators need to implement next and hasNext. The simplest is the Iterator.empty

object Iterator {
  object empty extends Iterator[Nothing] {
    def hasNext = false
    def next = error("next on empty iterator")
  }

There are contrete iterators which are constructed by functions in object Iterator, for instance fromArray, range, from (an iterator that never ends). Examples:

import Iterator._
fromArray(xs)
.zip(from(0))
.filter(case (x, i) => x > limit)
.map(case (x, i) => i)

import Iterator._
for ((x, i) <fromArray(
xs) zip from(0); x > limit)
yield i

Lazy values
Lazy fields will not get initialised until they are accessed, only allowed for concrete value definitions.

case class Employee(id: Int,
          name: String,
          managerId: Int) {
  lazy val manager: Employee = Db.get(managerId)
  lazy val team: List[Employee] = Db.team(id)
}

implicit
You can leave implicit params out when you call a function!

Scala will infer the parameter you mean based on the available identifiers that denote an implicit definition or parameter.

You can combine normal params and implicits, but implicits must come last and every parameter in the list must be implicit.

def sum[A](xs: List[A])(implicit m: Monoid[A]): A =
  if (xs.isEmpty) m.unit
  else m.add(xs.head, sum(xs.tail))

implicit object stringMonoid extends Monoid[String] {
  def add(x: String, y: String): String = x.concat(y)
  def unit: String = ""
}
implicit object intMonoid extends Monoid[Int] {
  def add(x: Int, y: Int): Int = x + y
  def unit: Int = 0
}

sum(List(1, 2, 3))   // This will infer from the type (Int) that the intMonoid should be used

Implicit Conversions
An example of an implicit conversion from integer to scala.Ordered:

implicit def int2ordered(x: Int): Ordered[Int] = new Ordered[Int] {
  def compare(y: Int): Int =
    if (x < y) 1
    else if (x > y) 1
    else 0
}

View bounded parameters <% are actually syntactic sugar for implicit conversions:

def sort[A <% Ordered[A]](xs: List[A]): List[A] =
...
// is a shorthand for
def sort[A](xs: List[A])(implicit c: A => Ordered[A]): List[A] = ...

Futures
Futures and promises and success, failure, wait, complete can all be googled!

I may add in a summary here later. But if you do learn them then also learn Akka.

Actually by 2022, its more likely you will be using Cats effect and FS2, or possibly ZIO.

Category theory
Once you have Scala syntax, you have to read up on some category theory and either Cats or Zio. Its too big a topic for this page. Do you really have to? Enough to bluff it at least.