Scala Functional Programming Cheat Sheet
Covers Scala functions and currying, pattern matching, functional collection operations, and core functional programming concepts like immutability.
Functions & Currying
Function values, higher-order functions, and partial application.
// Function values and higher-order functionsval square: Int => Int = x => x * xdef applyTwice(f: Int => Int, x: Int): Int = f(f(x))applyTwice(square, 3) // => 81// Partial application / curryingdef add(a: Int)(b: Int): Int = a + bval addFive = add(5) _addFive(10) // => 15// Immutable valuesval name: String = "Scala" // val is immutable; var is mutable
Pattern Matching
Destructuring case classes and matching with guards.
sealed trait Shapecase class Circle(radius: Double) extends Shapecase class Rectangle(width: Double, height: Double) extends Shapedef area(shape: Shape): Double = shape match { case Circle(r) => math.Pi * r * r case Rectangle(w, h) => w * h}// Matching with guardsdef describe(n: Int): String = n match { case 0 => "zero" case x if x > 0 => "positive" case _ => "negative"}
Functional Collections
map, filter, fold, and for-comprehensions.
val numbers = List(1, 2, 3, 4, 5)val doubled = numbers.map(_ * 2) // List(2, 4, 6, 8, 10)val evens = numbers.filter(_ % 2 == 0) // List(2, 4)val total = numbers.foldLeft(0)(_ + _) // 15val sum = numbers.sum // 15// for-comprehensionval pairs = for { x <- List(1, 2) y <- List("a", "b")} yield (x, y)// List((1,a), (1,b), (2,a), (2,b))
Core FP Concepts
Foundational ideas behind Scala's functional style.
- Immutability- val bindings and immutable collections (List, Vector) are the default; prefer them over var and mutable collections
- case class- Auto-generates equals, hashCode, toString, and copy(); ideal for immutable data
- Option[T]- Represents an optional value as Some(value) or None, avoiding null and NullPointerException
- Higher-order functions- Functions that take or return other functions, e.g. map, filter, and foldLeft
- for-comprehension- Syntactic sugar over flatMap/map/withFilter for chaining monadic operations like Option, List, and Future
- Pattern matching- match expressions destructure case classes and sealed traits, with exhaustiveness checked at compile time
Tail Recursion with @tailrec
Compiler-verified self-tail-calls that compile down to a loop instead of growing the stack.
import scala.annotation.tailrecdef factorial(n: Int): BigInt = { @tailrec def loop(acc: BigInt, k: Int): BigInt = if (k <= 1) acc else loop(acc * k, k - 1) loop(1, n)}// @tailrec fails to compile if the annotated method isn't in tail// position (e.g. calling itself inside a try, or not as the last// expression), catching would-be StackOverflowError bugs at compile time.
Typed Errors with Either
Right-biased Either composed through a for-comprehension for short-circuiting validation.
def parseAge(s: String): Either[String, Int] = s.toIntOption.toRight(s"'$s' is not a number") .filterOrElse(_ >= 0, "age cannot be negative")def validate(name: String, ageStr: String): Either[String, (String, Int)] = for { _ <- Either.cond(name.nonEmpty, (), "name is required") age <- parseAge(ageStr) } yield (name, age)validate("Ada", "36") // Right(("Ada", 36))validate("Ada", "-5") // Left("age cannot be negative")
Ad-hoc Polymorphism with Type Classes
The trait-plus-implicit-instance pattern for adding behavior to types without touching their definitions.
trait Show[A] { def show(a: A): String}object Show { implicit val intShow: Show[Int] = (a: Int) => a.toString implicit val stringShow: Show[String] = (a: String) => a def apply[A](implicit ev: Show[A]): Show[A] = ev}def render[A](a: A)(implicit s: Show[A]): Unit = println(s.show(a))render(42) // resolves intShow implicitlyrender("hello") // resolves stringShow implicitly// Scala 3 equivalent: `given Show[Int] with { def show(a: Int) = a.toString }`// and `def render[A](a: A)(using s: Show[A])`
Lazy Values, LazyList & Views
Deferred, memoized computation for infinite sequences and allocation-free chained transforms.
// Computed once, only on first accesslazy val expensive: Int = { println("computing..."); 42 }// LazyList: lazy, memoized, potentially infinite sequence (2.13+, replaces Stream)val fibs: LazyList[BigInt] = BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map { case (a, b) => a + b }fibs.take(10).toList// .view avoids materializing intermediate collections for chained opsval result = (1 to 1000000).view.map(_ * 2).filter(_ % 3 == 0).take(5).toList
Advanced FP Concepts
Vocabulary and mechanisms beyond the beginner FP toolkit.
- Type class pattern- trait + implicit instances, giving ad-hoc polymorphism (add behavior for a type) without modifying that type or using inheritance
- @tailrec- Compiler-enforced guarantee that a recursive call is in tail position and gets optimized into a loop
- Either[L, R]- Right-biased since Scala 2.12, so map/flatMap and for-comprehensions operate on the Right case, making it a natural typed-error channel
- LazyList- Lazily evaluated, memoized, potentially infinite sequence; the successor to the deprecated Stream
- Variance annotations (+A, -A)- Declare a generic type parameter covariant or contravariant, controlling how subtyping of List[A] relates to subtyping of A
- By-name parameters (=> A)- An argument whose expression is re-evaluated at each use site rather than once at call time, used to build control-structure-like APIs
- Extractors (unapply)- Defining unapply on any object makes it usable as a pattern in match expressions, not just case classes
- Implicit resolution- The compiler searches local scope, imports, and companion objects for an implicit/given instance; more specific instances win over general ones
Use sealed traits with case classes for algebraic data types — the compiler warns on non-exhaustive match expressions, catching missing cases at compile time instead of runtime.