Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

FP Design Patterns

Functional programming emphasizes the use of functions, immutability, and expressions over statements. Many traditional design patterns can be adapted to a functional style in Scala. Here are examples of functional programming design patterns in Scala:

1. Function Composition

Function composition is a fundamental concept in functional programming, where multiple functions are combined to produce a new function.

val addOne: Int => Int = _ + 1
val double: Int => Int = _ * 2

val addOneAndDouble: Int => Int = addOne.andThen(double)

// Usage
println(addOneAndDouble(3)) // 8 (3 + 1, then 4 * 2)

2. Higher-Order Functions

Higher-order functions are functions that take other functions as parameters or return functions as results.

def applyOperation(x: Int, y: Int, operation: (Int, Int) => Int): Int = operation(x, y)

val add: (Int, Int) => Int = _ + _
val multiply: (Int, Int) => Int = _ * _

// Usage
println(applyOperation(3, 4, add)) // 7
println(applyOperation(3, 4, multiply)) // 12

3. Currying

Currying transforms a function with multiple parameters into a series of functions, each with a single parameter.

def add(x: Int)(y: Int): Int = x + y

val addThree: Int => Int = add(3)

// Usage
println(addThree(4)) // 7
println(add(2)(3)) // 5

4. Partial Function Application

Partial function application involves fixing a few arguments of a function, producing another function of smaller arity.

def multiply(x: Int, y: Int, z: Int): Int = x * y * z

val multiplyBy2And3: Int => Int = multiply(2, 3, _)

// Usage
println(multiplyBy2And3(4)) // 24 (2 * 3 * 4)

5. Memoization

Memoization is an optimization technique to cache the results of expensive function calls and return the cached result when the same inputs occur again.

def memoize[I, O](f: I => O): I => O = {
  val cache = scala.collection.mutable.Map[I, O]()
  (input: I) =>
    cache.getOrElseUpdate(input, f(input))
}

val fib: Int => Int = {
  def fibHelper(n: Int): Int = n match {
    case 0 => 0
    case 1 => 1
    case _ => fib(n - 1) + fib(n - 2)
  }
  memoize(fibHelper)
}

// Usage
println(fib(10)) // 55

6. Functor

A functor is a structure that can be mapped over. In Scala, the map method on collections is an example.

val list = List(1, 2, 3)
val doubledList = list.map(_ * 2)

// Usage
println(doubledList) // List(2, 4, 6)

7. Monad

A monad is a design pattern used to handle program-wide concerns in a functional way. The flatMap method is used to chain operations.

val list = List(1, 2, 3)
val result = list.flatMap(x => List(x, x * 2))

// Usage
println(result) // List(1, 2, 2, 4, 3, 6)

8. Option Monad

The Option monad is used to represent optional values, providing a way to handle the absence of a value.

val someValue: Option[Int] = Some(5)
val noValue: Option[Int] = None

val result = someValue.map(_ * 2).getOrElse(0)
val result2 = noValue.map(_ * 2).getOrElse(0)

// Usage
println(result) // 10
println(result2) // 0

9. Either Monad

The Either monad is used to represent a value of one of two possible types (a disjoint union). It is often used for error handling.

def divide(x: Int, y: Int): Either[String, Int] = {
  if (y == 0) Left("Division by zero")
  else Right(x / y)
}

val result = divide(10, 2) match {
  case Right(value) => s"Result: $value"
  case Left(error) => s"Error: $error"
}

val result2 = divide(10, 0) match {
  case Right(value) => s"Result: $value"
  case Left(error) => s"Error: $error"
}

// Usage
println(result) // Result: 5
println(result2) // Error: Division by zero

10. Lazy Evaluation

Lazy evaluation is a technique where expressions are not evaluated until their results are needed.

lazy val expensiveComputation: Int = {
  println("Computing...")
  42
}

// Usage
println("Before accessing lazy value")
println(expensiveComputation) // "Computing..." followed by 42
println(expensiveComputation) // 42 (without recomputing)

11. Type Classes

Type classes enable ad-hoc polymorphism in a functional way.

trait Show[A] {
  def show(a: A): String
}

object ShowInstances {
  implicit val intShow: Show[Int] = (a: Int) => a.toString
  implicit val stringShow: Show[String] = (a: String) => a
}

object Show {
  def apply[A](implicit instance: Show[A]): Show[A] = instance

  def show[A: Show](a: A): String = Show[A].show(a)
}

// Usage
import ShowInstances._

println(Show.show(123)) // "123"
println(Show.show("Hello")) // "Hello"