Functional Programming

This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

Swift is not a purely functional language, but its value types, first-class functions, and rich set of higher-order algorithms make a functional style — immutable data, pure functions, composition over mutation — a natural fit alongside its object-oriented and protocol-oriented features.

Immutability and Pure Functions

struct Point { let x, y: Double }             // an immutable value type: every mutation produces a new Point

func translated(_ point: Point, dx: Double, dy: Double) -> Point {
    Point(x: point.x + dx, y: point.y + dy)    // pure: same inputs always produce the same output, no side effects
}

let origin = Point(x: 0, y: 0)
let moved = translated(origin, dx: 3, dy: 4)   // origin is untouched -- moved is a new value

Declaring with let and favoring value types (structs, enums — see Structures and Classes) over classes makes "changing" a value mean producing a new one rather than mutating shared state, which is what makes a function like translated(_:dx:dy:) pure: it reads only its arguments, has no observable side effect, and always returns the same result for the same inputs — callers can reason about it, test it, and run it concurrently without synchronization, unlike a method that mutates a shared class instance.

First-Class and Higher-Order Functions

func square(_ x: Int) -> Int { x * x }
let operation: (Int) -> Int = square           // a function used as an ordinary value

func apply(_ f: (Int) -> Int, to values: [Int]) -> [Int] {
    values.map(f)                               // apply is itself higher-order: it takes a function as a parameter
}
apply(square, to: [1, 2, 3])                    // [1, 4, 9]

func adder(_ amount: Int) -> (Int) -> Int {     // returns a function -- also makes apply's caller higher-order
    { value in value + amount }
}
let addFive = adder(5)
addFive(10)                                     // 15

A function that can be stored in a variable, passed as an argument, or returned from another function is first-class — true of every Swift function and closure. A higher-order function either takes a function as a parameter (apply(:to:)) or returns one (adder(:)); see Closures for the closure-expression syntax ({ value in …​ }) used to write one inline.

Composition, Currying and Partial Application

infix operator >>>: AdditionPrecedence
func >>> <A, B, C>(_ f: @escaping (A) -> B, _ g: @escaping (B) -> C) -> (A) -> C {
    { a in g(f(a)) }                            // custom composition operator: f then g
}

let double = { (x: Int) in x * 2 }
let increment = { (x: Int) in x + 1 }
let doubleThenIncrement = double >>> increment
doubleThenIncrement(5)                          // 11 -- (5 * 2) + 1

func curriedAdd(_ a: Int) -> (Int) -> Int {     // a curried function: takes its arguments one at a time
    { b in a + b }
}
let addTen = curriedAdd(10)                     // partial application: fixing the first argument
addTen(32)                                       // 42

Function composition builds a new function by feeding one function’s output into another’s input; the custom >>> operator above is one common convention for writing that left-to-right, building on the operator-overloading tools in Advanced Operators. Currying is writing a multi-argument function as a chain of single-argument functions (curriedAdd(_:) returns a function rather than taking two parameters directly), which makes partial application — fixing some arguments now and supplying the rest later, as addTen does — fall out naturally, without any dedicated language feature for it.

Recursion

func factorial(_ n: Int) -> Int {
    n <= 1 ? 1 : n * factorial(n - 1)           // no built-in tail-call optimization guarantee -- deep recursion
}                                                 // can still overflow the call stack for large n

indirect enum Expr {
    case value(Int)
    case add(Expr, Expr)
}
func evaluate(_ expr: Expr) -> Int {
    switch expr {
    case .value(let v): v
    case .add(let a, let b): evaluate(a) + evaluate(b)   // recursive descent over a recursive (indirect) enum
    }
}

Recursion — a function calling itself, directly or through an indirect enum’s recursive case (see Enumerations) — is the functional-style alternative to a loop with mutable state, and reads naturally over tree-shaped data. Swift makes no guarantee of tail-call optimization, so an iterative loop remains the right choice when recursion depth could be large or unbounded.

map/filter/reduce Pipelines and lazy

let numbers = Array(1...1_000_000)

let result = numbers
    .lazy                                        // defers every step below to run element-by-element, on demand
    .filter { $0.isMultiple(of: 3) }
    .map { $0 * $0 }
    .prefix(5)                                    // only the first 5 matches are ever actually computed
Array(result)                                     // [9, 36, 81, 144, 225]

let total = numbers.reduce(0, +)                  // sums the whole (non-lazy) array eagerly

Chaining map/filter/reduce (covered per-collection in Collections) expresses a data transformation as a pipeline of independent steps rather than a hand-written loop with an accumulator. Each step in an eager chain allocates a full intermediate array, though — .lazy switches the same chain to a LazySequence/LazyCollection that defers every operation until an element is actually demanded, which matters when only a prefix of a large or infinite sequence is ever consumed, as .prefix(5) does above.

Optional and Result as Containers

func parseInt(_ text: String) -> Int? { Int(text) }
func reciprocal(_ n: Int) -> Double? { n == 0 ? nil : 1.0 / Double(n) }

let value = parseInt("8")
    .map { $0 * 2 }                               // Optional.map: transform the value if present, else stay nil
    .flatMap(reciprocal)                           // flatMap: chain another Optional-returning step, no double-wrapping

func fetchUser(id: Int) -> Result<String, Error> {
    id > 0 ? .success("user\(id)") : .failure(URLError(.badURL))
}
let greeting = fetchUser(id: 7)
    .map { "Hello, \($0)" }                        // Result.map: transform .success, pass .failure through untouched
    .flatMap { name in
        name.count > 3 ? .success(name) : .failure(URLError(.badURL))
    }

Optional and Result (see Optionals and Error Handling) are both, from a functional standpoint, containers that may or may not hold a value — Optional unconditionally, Result alongside a specific failure reason. Their map transforms a contained value without unwrapping it by hand; their flatMap chains another container-returning step without producing a nested Optional<Optional<T>> or Result<Result<…​>, …​>, the same "flatten as you go" shape Sequence.flatMap provides for collections. Both types compose naturally with protocols and actors — an async function returning Result, or a Sendable value type flowing through an actor boundary — rather than being at odds with Swift’s other paradigms; see Protocols and Actors, Isolation and Sendable.

See Also

  • Closures — closure-expression syntax, capture semantics, and @escaping/@Sendable, the mechanics behind every higher-order function above.

  • Collections — the full Sequence/Collection algorithm set (map, filter, reduce, compactMap, and more) these pipelines draw from.

  • Optionals — Optional in full, including ?? and optional chaining.

  • Error Handling — Result in full, including converting between Result and throws.