← Scala EnglishChapter 10 of 13

Functional Programming

## Learning Objectives - Understand functional programming principles - Master higher-order functions - Learn closures - Work with currying - Understand partial application ## Functional Programming Principles ### Pure Functions A pure function: - Returns the same result for the same inputs - Has no side effects (no I/O, no mutation) ```scala // Pure function def add(a: Int, b: Int): Int = a + b // Impure function (has side effect) var counter = 0 def nextInt(): Int = { counter += 1 counter } ``` ### Immutability Prefer immutable data structures: ```scala // Immutable approach val original = List(1, 2, 3) val added = 0 :: original // New list: List(0, 1, 2, 3) // Mutable approach (avoid when possible) import scala.collection.mutable val buffer = mutable.ListBuffer(1, 2, 3) buffer.prepend(0) // Mutates buffer ``` ## Higher-Order Functions Functions that take functions as parameters or return functions: ```scala def applyTwice(f: Int => Int, x: Int): Int = f(f(x)) def double(x: Int): Int = x * 2 def increment(x: Int): Int = x + 1 applyTwice(double, 3) // 12 (3 * 2 * 2) applyTwice(increment, 3) // 5 (3 + 1 + 1) ``` ### Common Higher-Order Functions ```scala val numbers = List(1, 2, 3, 4, 5) // map - transform each element numbers.map(_ * 2) // List(2, 4, 6, 8, 10) // filter - keep elements satisfying predicate numbers.filter(_ % 2 == 0) // List(2, 4) // flatMap - map then flatten numbers.flatMap(x => List(x, -x)) // List(1, -1, 2, -2, 3, -3, 4, -4, 5, -5) // fold/reduce - combine elements numbers.fold(0)(_ + _) // 15 (sum) numbers.reduce(_ * _) // 120 (product) // collect - pattern match and transform numbers.collect { case x if x % 2 == 0 => x * 2 } // List(4, 8) ``` ## Closures A closure captures variables from its environment: ```scala def multiplier(factor: Int): Int => Int = { (x: Int) => x * factor // factor is captured } val triple = multiplier(3) triple(5) // 15 val double = multiplier(2) double(5) // 10 ``` ### Mutable Capture ```scala var factor = 1 val incrementAndMultiply: Int => Int = (x: Int) => { factor += 1 x * factor } incrementAndMultiply(5) // 6 (factor becomes 2) incrementAndMultiply(5) // 15 (factor becomes 3) ``` ## Currying Converting a function with multiple parameters into a chain of functions: ```scala def add(a: Int)(b: Int): Int = a + b add(3)(5) // 8 val addFive = add(5) _ addFive(3) // 8 addFive(10) // 15 ``` ### Currying Benefits ```scala // Without currying def withLogging(f: () => Unit): () => Unit = { () => { println("Before") f() println("After") } } // With currying def withLogging(f: => Unit): Unit = { println("Before") f println("After") } def greet(): Unit = println("Hello!") withLogging(greet) ``` ## Partial Application Providing some arguments, leaving others for later: ```scala def multiply(a: Int, b: Int, c: Int): Int = a * b * c val multiplyBy2 = multiply(2, _, 3) // Partially applied multiplyBy2(5) // 30 (2 * 5 * 3) val multiplyBy6 = multiply(2, 3, _) // Partially applied multiplyBy6(5) // 30 (2 * 3 * 5) ``` ## Function Composition ### Compose ```scala val double = (x: Int) => x * 2 val addOne = (x: Int) => x + 1 val doubleAddOne = double compose addOne // (x + 1) * 2 doubleAddOne(5) // 12 ``` ### AndThen ```scala val double = (x: Int) => x * 2 val addOne = (x: Int) => x + 1 val doubleThenAddOne = double andThen addOne // (x * 2) + 1 doubleThenAddOne(5) // 11 ``` ### Using with for-expressions ```scala case class Person(name: String, age: Int, city: String) val people = List( Person("Alice", 30, "NYC"), Person("Bob", 25, "LA"), Person("Charlie", 35, "NYC") ) val nycNames = for { p <- people if p.city == "NYC" } yield p.name // List("Alice", "Charlie") ``` ## Pure Functional Data Structures ### Persistent Lists ```scala sealed trait List[+A] case object Nil extends List[Nothing] case class Cons[A](head: A, tail: List[A]) extends List[A] object List { def apply[A](as: A*): List[A] = if (as.isEmpty) Nil else Cons(as.head, apply(as.tail: _*)) def foldRight[A, B](as: List[A], z: B)(f: (A, B) => B): B = as match { case Nil => z case Cons(x, xs) => f(x, foldRight(xs, z)(f)) } } ``` ## Lazy Evaluation ### Lazy Evaluation with `lazy` ```scala lazy val expensive = { println("Computing...") 42 } println("Before") println(expensive) // Computing... then 42 println(expensive) // Just 42 ``` ### Lazy Lists (Streams) ```scala import scala.collection.immutable.LazyList def fibonacci: LazyList[Int] = { def go(a: Int, b: Int): LazyList[Int] = a #:: go(b, a + b) go(0, 1) } fibonacci.take(10).toList // List(0, 1, 1, 2, 3, 5, 8, 13, 21, 34) ``` ## Function Types | Type | Description | |------|-------------| | `Int => Int` | Function from Int to Int | | `(Int, Int) => Int` | Function with two Int params | | `() => Unit` | Function with no params | | `Int => Boolean` | Predicate on Int | | `(Int, Int) => Boolean` | Binary predicate | ## Methods vs Functions ```scala class Calculator { def add(a: Int, b: Int): Int = a + b } val calc = new Calculator() // Method - eta expansion converts to function val addFunction: (Int, Int) => Int = calc.add _ // Direct function val add = (a: Int, b: Int) => a + b ``` ## Tail Recursion Optimization ```scala def factorial(n: Int): BigInt = { @annotation.tailrec def loop(acc: BigInt, n: Int): BigInt = { if (n <= 1) acc else loop(acc * n, n - 1) } loop(1, n) } ``` ## Summary - Functional programming emphasizes pure functions and immutability - Higher-order functions take or return functions - Closures capture environment variables - Currying splits multi-parameter functions into chains - Partial application fixes some arguments for later - Function composition creates new functions from existing ones - Lazy evaluation defers computation until needed - Tail recursion is optimized to avoid stack overflow - Prefer val over var, immutable over mutable

Comments

Comments powered by Giscus

To enable comments, add your Giscus embed code here.

Learn more about Giscus →