Why Scala?


This is a working list — features I reach for regularly and would miss in any other language.

Rich type system

Unit and Nothing do real work. Nothing is the bottom type: an Exception has type Nothing, which means it can be used in place of any return type without a cast. This is not just a curiosity — it’s what makes throw expressions compose correctly with the type checker.

Pattern matching on almost anything

List(1,2,3) match {
  case Nil        => println("The list is empty")
  case x @ h :: _ => println(s"$x starts from: $h")
}
// List(1, 2, 3) starts from: 1

Pattern matching works on case classes, sealed hierarchies, tuples, collections, and arbitrary types via custom unapply. The @ binding captures the whole structure while also destructuring it.

Case classes replace Java boilerplate

In Java:

class Point {
  int x;
  int y;

  Point(int x, int y) {
        this.x = x;
        this.y = y;
  }
  // equals, hashcode, toString...
}
Point p = new Point(10, 20);

In Scala:

case class Point(x: Int, y: Int)
val p = Point(10, 20)
// p: Point = Point(x = 10, y = 20)

equals, hashcode, toString, unapply, and copy are generated by the compiler. No annotation processor, no framework — it is part of the language.

Compiler exhaustiveness checks

Seal a trait and the compiler tracks every case. Forget one and it is a compile error:

sealed trait Color
case object Red extends Color
case object Yellow extends Color
case object Green extends Color

def show(c: Color): String = c match {
  case Red => "Red"
  case Green => "Green"
}
// error: match may not be exhaustive.
// It would fail on the following input: Yellow
// Applicable -Wconf / @nowarn filters for this fatal warning: msg=<part of the message>, cat=other-match-analysis, site=repl.MdocSession.MdocApp.show
// def show(c: Color): String = c match {
//                              ^

This is not a runtime warning — it is caught at compile time.

Other features worth naming

  • Currying and partial application — functions can be partially applied to produce new functions
  • Higher-order functions — functions are first-class values
  • Tail recursion — the compiler optimizes tail-recursive methods to loops via @tailrec
  • Implicits and extension methods — add behavior to existing types without subclassing
  • Generics and higher-kinded types — abstract over type constructors, not just types
  • Immutable collections — the standard library defaults to immutable; mutable variants are opt-in
  • Sealed traits — model closed algebras with compiler-enforced exhaustiveness
  • Macros and derivation — generate code at compile time from type information
  • sbt plugins — build configuration is Scala code, not XML
  • JVM and JS targets — the same source can compile to the JVM or Scala.js
  • JVM interop — full access to the Java ecosystem without ceremony
  • Unchecked exceptions — no forced try/catch; error handling is done through types
  • Compiler optimizationsfinal val and @inline give low-level control when needed
  • _ syntax — wildcard for imports, anonymous function parameter, existential type; context determines meaning
  • Imports anywhere — imports can appear inside methods, expressions, or blocks, scoped to where they’re needed

Will add more once recalled.