Traits, Concurrency & Build Tools
Traits & Objects
trait Named { def name: String }
trait Aged { def age: Int; def isAdult: Boolean = age >= 18 } // concrete default method
class Person(val name: String, val age: Int) extends Named with Aged
// object — a singleton; a "companion object" shares its name with a class
// and can access its private members, commonly hosting factory methods
class Connection private (val url: String)
object Connection {
def apply(url: String): Connection = new Connection(url) // Connection("...") works
}Either for Error Handling
def parseAge(input: String): Either[String, Int] =
input.toIntOption match {
case Some(age) if age >= 0 => Right(age)
case Some(_) => Left("age cannot be negative")
case None => Left(s"'$input' is not a number")
}
// Unlike Option, Either carries WHY something failed, not just that it did
parseAge("30") match { case Right(a) => println(a); case Left(e) => println(e) }
parseAge("abc") match { case Right(a) => println(a); case Left(e) => println(e) }Tail Recursion
import scala.annotation.tailrec
@tailrec // compiler VERIFIES this is truly tail-recursive, fails to compile if not —
// catches a StackOverflowError risk on large inputs before runtime
def sum(numbers: List[Int], acc: Int = 0): Int = numbers match {
case Nil => acc
case head :: tail => sum(tail, acc + head) // recursive call is the LAST operation
}sbt & Spark
sbt compile
sbt test
sbt run
# build.sbt
# libraryDependencies += "org.apache.spark" %% "spark-sql" % "3.5.0"
# Scala is Spark's native API — favoring pure functions over immutable data
# matters especially here: Spark distributes work across nodes/partitions,
# and can re-execute a task after a failure — a function with side effects
# or execution-order assumptions can produce inconsistent results when
# re-run, while a pure function over immutable data always behaves the same.Keep your own version of these notes — editable, searchable, and organised by your stack.
Start free