Interlude - Scala

What is Scala?

Scala is a hybrid programming language running on the Java Virtual Machine (JVM) that combines two major paradigms:

  • Object Oriented: Every value is an object, including primitives like Ints.
  • Functional: Every function is a value, meaning functions can be passed around just like data.
  • Key Concept: Even methods are considered values in Scala.

Syntax Basics

  • Printing: Uses println("...") for standard output.
    • Feature: If you provide multiple arguments to println(1, 2, 3), Scala automatically treats them as a tuple and prints them in parentheses (1, 2, 3).
    • Formatting: printf is available for C-style formatting (e.g., %d for integers).
  • String Interpolation: Allows you to embed variables directly into strings.
    • Usage: Prefix the string with s or f.
    • Example: s"Value is ${2+2}" evaluates to "Value is 4".
    • Syntax: Braces {} are optional if the expression is a simple variable (e.g., $x), but required for expressions.

Variables: var vs val

Scala makes a strict distinction between the reference (the pointer) and the object (the data).

  • var (Variable): A mutable reference. You can change where this name points to (e.g., x = 5, then x = 6).
  • val (Value): An immutable reference. Once assigned, this name effectively becomes a constant pointer.

Critical Conceptual Distinctions:

  • val reference to Mutable Object: You cannot point the val to a new object, but you can modify the internals of the object it points to (e.g., adding an item to a mutable list).
  • var reference to Immutable Object: You cannot change the object itself (because it is immutable), but you can make the var point to a completely different object (e.g., x pointed to 3, now it points to 4).

Collections: Arrays

  • Syntax: Defined using square brackets Array[Int], unlike Java's <Int>.
  • Mixed Types: Array[Any] can hold mixed types (e.g., Integers and Doubles) because Any is the universal supertype in Scala.
  • Operators (Immutability):
    • ++: Concatenates two arrays to create a new array.
    • :+: Appends a single value to create a new array.
    • Note: Since arrays have a fixed size, these operators never modify the original array; they always return a new copy. Attempting += on a standard array will fail unless you are reassigning a var.

Collections: Maps

Scala provides two distinct types of Maps, and it is crucial to know which one you are using.

Immutable Map (The Default)

  • Creation: Map("key" -> value).
  • Behavior: You cannot modify the object.
  • Updating Strategy: To "add" an item, you must use a var reference.

    var m = Map("a" -> 1)
    m += ("b" -> 2)
    
    • Explanation: This += is syntactic sugar for m = m + pair. It creates a new map containing the old data plus the new pair, and reassigns m to point to it.
    • Efficiency (Trie): Creating a "new" immutable map is actually very fast (\(O(h)\)). It uses a "Trie" (tree) structure where the new map shares most of the structure of the old map, rather than copying all data. It avoids tree rotations.

Mutable Map

  • Creation: scala.collection.mutable.Map(...).
  • Behavior: You can modify the object in place.
  • Updating Strategy: You can use a val reference.

    val m = mutable.Map("a" -> 1)
    m += ("b" -> 2)
    
    • Explanation: This calls the actual += method defined on the object, which mutates the existing data structure in memory.

Loops

Loops in Scala are expressions, meaning they can evaluate to a value.

  • Ranges:
    • 1 to 10: Inclusive (1, 2, ... 10).
    • 0 until 10: Exclusive (0, 1, ... 9).
    • by: Sets the step size (e.g., by 2 or by -1).
  • Yield (Functional Looping):
    • Placing the keyword yield after a loop definition turns it into a transformation (map).
    • Result: It returns a new collection (like a Vector) containing the results of each iteration.
  • Iterating Maps:
    • You can unpack keys and values directly in the loop syntax: for ((k, v) <- map) ....

Functions

  • Definition: def name(param: Type): ReturnType = { body }.
  • Return Values: The return keyword is optional. The result of the last expression in the block is automatically returned.
  • Unit Type: The Scala equivalent of void. It represents the absence of a value (e.g., for functions that just print to the screen).

Anonymous Functions (Lambdas)

  • Syntax: (x: Int) => x * x.
  • Underscore Syntax: A concise shorthand for lambdas where parameters are used only once.
    • Example: List(1, 2, 3).map(_ + 1)
    • Meaning: The _ stands in for the parameter x => x + 1.
    • Order: The first _ is the first parameter, the second _ is the second, etc..

Pattern Matching (match)

A powerful control structure that goes far beyond a C-style switch. It can match:

  • Values: case 1 => "One"
  • Types: case s: String => s (Matches if the input is a String)
  • Structures: case List(y) => y (Matches if the input is a List containing exactly one element).
  • Note: The slides use a "Discworld Trolls" analogy (counting "1, 2, 3, many, lots") to illustrate falling through cases.

Option Type (Option, Some, None)

Scala's type-safe alternative to null pointers. An Option[T] is a container that holds either:

  • Some(value): The value exists.
  • None: The value is missing.
  • Retrieving Values:
    • .get: Returns the value or throws an exception if None (unsafe).
    • Pattern Matching: The preferred way to handle Options safely:

      opt match {
        case Some(x) => x + 1
        case None => 0
      }
      

Java Interoperability

Scala runs on the JVM and can use standard Java libraries, but Java collections (like ArrayList) do not natively work with Scala's for loops or higher-order functions.

  • The Solution: Use JavaConverters.
  • Step 1: import scala.collection.JavaConverters._
  • Step 2: Call .asScala on any Java collection to convert it into a Scala-compatible iterator or collection.