पाठ 24 / 25

Idiomatic Scala and Common Pitfalls

Write clear Scala and avoid frequent mistakes.

Writing Scala that others can read

Idiomatic Scala favours immutability (val, immutable collections, case classes), expressions over statements, ADTs and pattern matching for domain modelling, Option/Either instead of null and exceptions, small pure functions and explicit types on public APIs. Format consistently with scalafmt, and use scalafix and compiler warnings (-Wunused:all, -Werror in CI) to catch problems early. Common pitfalls: calling .get on Option or .head on possibly empty lists (use getOrElse, headOption or pattern matching); overusing implicits or givens so code becomes hard to follow; writing "Haskell in Scala" or "Java in Scala" inconsistently across a team instead of agreeing on a style; forgetting that Future runs eagerly (and creating futures inside a for comprehension, which makes them sequential); blocking inside the default execution context; using return in lambdas; overly clever type-level code that slows compilation and confuses colleagues; using List for random access; and comparing case classes containing Array (arrays use reference equality). Prefer the simplest feature that solves the problem.

Unidiomatic versus idiomatic

The same lookup written two ways.

case class Customer(id: String, email: String, tier: String)

// unidiomatic: nulls, var and .get
def findEmailBad(customers: List[Customer], id: String): String =
  var result: String = null
  for c <- customers do
    if c.id == id then result = c.email
  if result == null then throw new RuntimeException("not found")
  result.toLowerCase

// idiomatic: Option and an expression
def findEmail(customers: List[Customer], id: String): Option[String] =
  customers.find(_.id == id).map(_.email.toLowerCase)

val customers = List(Customer("c1", "ASHA@example.com", "gold"))
val greeting = findEmail(customers, "c1") match
  case Some(email) => s"Sending offer to $email"
  case None        => "No such customer"

// pitfall: .head on an empty list throws; prefer headOption
val firstGold: Option[Customer] = customers.filter(_.tier == "gold").headOption

// pitfall: arrays compare by reference inside case classes
case class Batch(ids: Array[String])
println(Batch(Array("a")) == Batch(Array("a")))      // false
case class SafeBatch(ids: Vector[String])
println(SafeBatch(Vector("a")) == SafeBatch(Vector("a")))   // true

Agree on a team style

Scala supports many styles, from "better Java" to pure functional programming. Teams are most productive when they agree on one level (for example, "Option and Either, Cats Effect for I/O, no custom type-level code") and enforce it in reviews.

त्वरित जाँच: Why can calling .get on an Option be dangerous?

  • It throws an exception when the Option is None
  • It is slow
  • It changes the Option
  • It returns null
Answer

It throws an exception when the Option is None — Option.get on None throws NoSuchElementException; use getOrElse, fold or pattern matching.