NexusCS

Scala

Programming Languages
Quick reference for Scala - a multi-paradigm language blending object-oriented and functional programming on the JVM.
functional
jvm
scala

Getting started

Introduction

Scala is a modern multi-paradigm programming language designed to express common programming patterns in a concise, elegant, and type-safe way. It seamlessly integrates features of object-oriented and functional languages.

Basic Syntax

// Immutable variable (preferred)
val x = 10
val name: String = "Scala"

// Mutable variable
var counter = 0
counter = 1

// Method definition
def add(a: Int, b: Int): Int = a + b
def greet(name: String) = s"Hello, $name"

// Type inference
val inferred = "I'm a String"

Quick Example

// Case class (immutable)
case class Person(name: String, age: Int)

// Pattern matching
def describe(p: Person) = p match {
  case Person(n, a) if a < 18 => s"$n is a minor"
  case Person(n, a) => s"$n is $a years old"
}

val john = Person("John", 25)
println(describe(john))

Variables & Definitions

Values & Variables

// Immutable (val)
val pi = 3.14
val numbers = List(1, 2, 3)

// Mutable (var)
var count = 0
count += 1

// Type annotations (optional)
val name: String = "Scala"
val age: Int = 30

Methods

// With explicit return type
def multiply(x: Int, y: Int): Int = {
  x * y
}

// Single expression (no braces)
def square(x: Int) = x * x

// Multiple parameters lists
def fold[A, B](list: List[A])(z: B)(f: (B, A) => B): B = {
  list.foldLeft(z)(f)
}

// Default parameters
def greet(name: String = "World") = s"Hello, $name"

// Named arguments
greet(name = "Scala")

Type Aliases

type UserId = String
type Result[T] = Either[String, T]

val id: UserId = "user-123"
val result: Result[Int] = Right(42)

Functions

Anonymous Functions

// Lambda syntax
val add = (x: Int, y: Int) => x + y
val square = (x: Int) => x * x

// Multi-line
val greet = (name: String) => {
  val message = s"Hello, $name"
  message.toUpperCase
}

// Type inference
List(1, 2, 3).map(x => x * 2)

Underscore Syntax

// Placeholder syntax
List(1, 2, 3).map(_ * 2)
List(1, 2, 3).filter(_ > 1)

// Multiple placeholders
val add = (_: Int) + (_: Int)
List(1, 2, 3).reduce(_ + _)

// With higher-order functions
val numbers = List(1, 2, 3)
numbers.map(_ * 2).filter(_ > 3)

Higher-Order Functions

// Function as parameter
def applyTwice(f: Int => Int, x: Int) = f(f(x))
applyTwice(_ * 2, 3) // 12

// Function as return value
def multiplier(factor: Int): Int => Int = {
  (x: Int) => x * factor
}
val triple = multiplier(3)
triple(4) // 12

// Currying
def add(x: Int)(y: Int) = x + y
val add5 = add(5)_
add5(3) // 8

Collections

Common Collections

// List (immutable, linked list)
val list = List(1, 2, 3, 4)
val empty = List.empty[Int]
val prepend = 0 :: list

// Vector (immutable, indexed)
val vector = Vector(1, 2, 3, 4)
val updated = vector.updated(0, 10)

// Set (immutable, unique elements)
val set = Set(1, 2, 3, 2) // Set(1, 2, 3)

// Map (immutable, key-value)
val map = Map("a" -> 1, "b" -> 2)
val value = map.get("a") // Some(1)

Collection Operations

val numbers = List(1, 2, 3, 4, 5)

// map - transform elements
numbers.map(_ * 2) // List(2, 4, 6, 8, 10)

// filter - select elements
numbers.filter(_ > 2) // List(3, 4, 5)

// flatMap - map + flatten
numbers.flatMap(n => List(n, n * 10))
// List(1, 10, 2, 20, 3, 30, 4, 40, 5, 50)

// fold - accumulate from left
numbers.foldLeft(0)(_ + _) // 15

// reduce - accumulate without initial
numbers.reduce(_ + _) // 15

// collect - filter + map
numbers.collect {
  case n if n % 2 == 0 => n * 2
} // List(4, 8)

Mutable Collections

import scala.collection.mutable

// Mutable List
val buffer = mutable.ListBuffer(1, 2, 3)
buffer += 4
buffer ++= List(5, 6)

// Mutable Set
val mutableSet = mutable.Set(1, 2, 3)
mutableSet += 4

// Mutable Map
val mutableMap = mutable.Map("a" -> 1)
mutableMap("b") = 2
mutableMap += ("c" -> 3)

Pattern Matching

Match Expressions

val number = 2

number match {
  case 1 => "one"
  case 2 => "two"
  case 3 => "three"
  case _ => "other"
}

// With guards
def describe(x: Int) = x match {
  case n if n < 0 => "negative"
  case 0 => "zero"
  case n if n > 0 => "positive"
}

// Multiple conditions
def classify(x: Any) = x match {
  case _: String => "string"
  case _: Int => "integer"
  case _ => "unknown"
}

Case Classes

// Define case class
case class Point(x: Int, y: Int)
case class Circle(center: Point, radius: Int)

// Auto-generated features:
// - Constructor parameters as fields
// - equals, hashCode, toString
// - copy method
// - companion object with apply

val p = Point(1, 2) // No 'new' needed
val p2 = p.copy(x = 5) // Point(5, 2)

// Pattern matching on case classes
def describe(shape: Any) = shape match {
  case Point(0, 0) => "origin"
  case Point(x, 0) => s"on x-axis at $x"
  case Point(0, y) => s"on y-axis at $y"
  case Point(x, y) => s"point at ($x, $y)"
  case Circle(center, r) => s"circle at $center"
}

Sealed Traits (ADTs)

// Algebraic Data Types
sealed trait Result
case class Success(value: Int) extends Result
case class Failure(error: String) extends Result
case object Pending extends Result

// Exhaustive pattern matching
def handle(result: Result): String = result match {
  case Success(v) => s"Got value: $v"
  case Failure(e) => s"Error: $e"
  case Pending => "Still processing..."
  // Compiler warns if cases are missing
}

// Sealed hierarchies
sealed trait Tree
case class Leaf(value: Int) extends Tree
case class Branch(left: Tree, right: Tree) extends Tree

For Comprehensions

Basic Syntax

// Simple iteration
for (i <- 1 to 5) {
  println(i)
}

// With yield (map)
val squares = for (i <- 1 to 5) yield i * i
// Vector(1, 4, 9, 16, 25)

// Multiple generators
for {
  i <- 1 to 3
  j <- 1 to 3
} yield (i, j)
// Vector((1,1), (1,2), (1,3), (2,1), ...)

Filters & Guards

// With filter
for {
  i <- 1 to 10
  if i % 2 == 0
} yield i
// Vector(2, 4, 6, 8, 10)

// Multiple filters
for {
  i <- 1 to 20
  if i % 2 == 0
  if i % 3 == 0
} yield i
// Vector(6, 12, 18)

// Intermediate values
for {
  i <- 1 to 5
  square = i * i
  if square > 10
} yield (i, square)

Desugaring to flatMap

// For comprehension
for {
  x <- List(1, 2, 3)
  y <- List(10, 20)
} yield x + y

// Desugars to:
List(1, 2, 3).flatMap { x =>
  List(10, 20).map { y =>
    x + y
  }
}

Option & Try

Option (Null Safety)

// Some or None
val some: Option[Int] = Some(42)
val none: Option[Int] = None

// Pattern matching
def describe(opt: Option[Int]) = opt match {
  case Some(value) => s"Got $value"
  case None => "Got nothing"
}

// Common operations
some.getOrElse(0) // 42
none.getOrElse(0) // 0

some.map(_ * 2) // Some(84)
none.map(_ * 2) // None

some.filter(_ > 40) // Some(42)
some.filter(_ > 50) // None

// For comprehension
for {
  a <- Some(10)
  b <- Some(20)
} yield a + b // Some(30)

Try (Exception Handling)

import scala.util.{Try, Success, Failure}

// Wrapping unsafe code
def divide(a: Int, b: Int): Try[Int] = Try(a / b)

divide(10, 2) // Success(5)
divide(10, 0) // Failure(ArithmeticException)

// Pattern matching
divide(10, 0) match {
  case Success(result) => s"Result: $result"
  case Failure(exception) => s"Error: ${exception.getMessage}"
}

// Operations
Try("123".toInt).getOrElse(0) // 123
Try("abc".toInt).getOrElse(0) // 0

// Chaining
for {
  a <- Try("10".toInt)
  b <- Try("20".toInt)
} yield a + b // Success(30)

Either (Success or Error)

// Right (success) or Left (error)
def divide(a: Int, b: Int): Either[String, Int] = {
  if (b == 0) Left("Division by zero")
  else Right(a / b)
}

divide(10, 2) // Right(5)
divide(10, 0) // Left("Division by zero")

// Pattern matching
divide(10, 0) match {
  case Right(result) => s"Result: $result"
  case Left(error) => s"Error: $error"
}

Traits & Objects

Traits

// Define trait
trait Greeter {
  def greet(name: String): String
}

// Implement trait
class EnglishGreeter extends Greeter {
  def greet(name: String) = s"Hello, $name"
}

// Trait with implementation
trait Logger {
  def log(message: String): Unit = {
    println(s"[LOG] $message")
  }
}

// Multiple traits (mixins)
class MyClass extends Greeter with Logger {
  def greet(name: String) = {
    log(s"Greeting $name")
    s"Hello, $name"
  }
}

Objects (Singletons)

// Singleton object
object Config {
  val apiUrl = "https://api.example.com"
  val timeout = 30

  def isProduction = sys.env.get("ENV") == Some("prod")
}

// Usage
Config.apiUrl

// Companion object
case class User(id: String, name: String)

object User {
  def create(name: String): User = {
    User(java.util.UUID.randomUUID.toString, name)
  }
}

val user = User.create("John")

Abstract Classes

// Abstract class
abstract class Animal {
  def name: String
  def sound: String

  def makeSound() = println(s"$name says $sound")
}

// Concrete class
class Dog(val name: String) extends Animal {
  def sound = "Woof!"
}

val dog = new Dog("Rex")
dog.makeSound() // Rex says Woof!

Implicits & Given-Using

Implicit Parameters (Scala 2)

// Define implicit value
implicit val defaultTimeout: Int = 30

// Method with implicit parameter
def fetchData(url: String)(implicit timeout: Int) = {
  s"Fetching $url with timeout $timeout"
}

// Called without explicit parameter
fetchData("https://api.com") // Uses defaultTimeout

// Explicit call
fetchData("https://api.com")(60)

Given-Using (Scala 3)

// Define given instance
given defaultTimeout: Int = 30

// Method with using parameter
def fetchData(url: String)(using timeout: Int) = {
  s"Fetching $url with timeout $timeout"
}

// Called without explicit parameter
fetchData("https://api.com")

Type Classes Pattern

// Type class
trait Serializer[A] {
  def serialize(value: A): String
}

// Instances
given Serializer[Int] with {
  def serialize(value: Int) = value.toString
}

given Serializer[String] with {
  def serialize(value: String) = s""""$value""""
}

// Generic method using type class
def toJson[A](value: A)(using serializer: Serializer[A]): String = {
  serializer.serialize(value)
}

toJson(42)       // "42"
toJson("hello")  // "\"hello\""

Futures & Async

Creating Futures

import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

// Create future
val future = Future {
  Thread.sleep(1000)
  42
}

// Immediate values
val success = Future.successful(42)
val failure = Future.failed(new Exception("Error"))

Future Operations

// map - transform result
val doubled = future.map(_ * 2)

// flatMap - chain futures
val chained = future.flatMap { value =>
  Future(value + 10)
}

// For comprehension
val combined = for {
  a <- Future(10)
  b <- Future(20)
  c <- Future(30)
} yield a + b + c

// Handling completion
future.onComplete {
  case Success(value) => println(s"Got $value")
  case Failure(exception) => println(s"Failed: $exception")
}

Combining Futures

val f1 = Future(10)
val f2 = Future(20)
val f3 = Future(30)

// Sequence - List[Future] to Future[List]
Future.sequence(List(f1, f2, f3))
// Future(List(10, 20, 30))

// Traverse - map + sequence
val numbers = List(1, 2, 3)
Future.traverse(numbers)(n => Future(n * 2))
// Future(List(2, 4, 6))

// Zip - combine two futures
f1.zip(f2) // Future((10, 20))

Functional Patterns

Currying

// Curried function
def add(x: Int)(y: Int) = x + y

val add5 = add(5)_
add5(3) // 8

// Multiple parameter lists
def fold[A, B](list: List[A])(zero: B)(op: (B, A) => B): B = {
  list.foldLeft(zero)(op)
}

val sum = fold(List(1, 2, 3))(0)(_ + _) // 6

Partial Application

def greet(greeting: String, name: String) = s"$greeting, $name"

val sayHello = greet("Hello", _: String)
sayHello("World") // Hello, World

// With multiple parameters
def process(a: Int, b: Int, c: Int) = a + b + c

val partial = process(10, _: Int, 30)
partial(20) // 60

Function Composition

// andThen
val addOne = (x: Int) => x + 1
val double = (x: Int) => x * 2

val addThenDouble = addOne andThen double
addThenDouble(3) // 8

// compose (reverse order)
val doubleFirst = addOne compose double
doubleFirst(3) // 7 (double then add)

Tail Recursion

import scala.annotation.tailrec

// Tail recursive function
@tailrec
def factorial(n: Int, acc: Int = 1): Int = {
  if (n <= 1) acc
  else factorial(n - 1, n * acc)
}

factorial(5) // 120

// Tail recursive sum
@tailrec
def sum(list: List[Int], acc: Int = 0): Int = list match {
  case Nil => acc
  case head :: tail => sum(tail, acc + head)
}

Common Gotchas

Mutable vs Immutable

// Immutable by default
val list = List(1, 2, 3)
// list += 4 // ERROR: reassignment to val

// Need mutable variant
import scala.collection.mutable
val buffer = mutable.ListBuffer(1, 2, 3)
buffer += 4 // OK

// Or use var with immutable collection
var immutableList = List(1, 2, 3)
immutableList = immutableList :+ 4 // Creates new list

Return Keyword

// Avoid explicit return
def add(a: Int, b: Int): Int = {
  return a + b // BAD: unnecessary
}

// Prefer expression-oriented
def add(a: Int, b: Int): Int = a + b // GOOD

// Return exits early from lambda
List(1, 2, 3).map { x =>
  return x * 2 // BAD: exits outer method!
}

// Use without return
List(1, 2, 3).map(x => x * 2) // GOOD

Null vs None

// Avoid null
var name: String = null // BAD

// Use Option instead
var name: Option[String] = None // GOOD

// Safe navigation
val length = name.map(_.length).getOrElse(0)

// Pattern matching
name match {
  case Some(n) => s"Name is $n"
  case None => "No name"
}

Equality

// Case classes compare by value
case class Person(name: String, age: Int)

val p1 = Person("John", 30)
val p2 = Person("John", 30)
p1 == p2 // true

// Regular classes compare by reference
class User(val name: String)

val u1 = new User("John")
val u2 = new User("John")
u1 == u2 // false (different instances)

Type System

Variance

// Covariant (use +)
trait Producer[+A] {
  def produce(): A
}

// Contravariant (use -)
trait Consumer[-A] {
  def consume(a: A): Unit
}

// Invariant (no annotation)
trait Box[A] {
  def get: A
  def set(a: A): Unit
}

// Example
class Animal
class Dog extends Animal

val dogProducer: Producer[Dog] = ???
val animalProducer: Producer[Animal] = dogProducer // OK

Type Bounds

// Upper bound (A must be subtype of Animal)
def process[A <: Animal](animal: A) = ???

// Lower bound (A must be supertype of Dog)
def add[A >: Dog](animal: A) = ???

// Context bound (requires implicit/given)
def sort[A: Ordering](list: List[A]) = list.sorted

// View bound (Scala 2.x, deprecated)
def print[A <% String](value: A) = println(value)

Generic Classes

// Simple generic
class Box[A](val value: A) {
  def get: A = value
}

val intBox = new Box(42)
val stringBox = new Box("hello")

// Multiple type parameters
class Pair[A, B](val first: A, val second: B)

val pair = new Pair(1, "one")

Also see