Functional Programming in Swift: From A to Z
Functional programming (FP) is a paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. Swift, being a multi-paradigm language, perfectly supports the functional style, especially in iOS and macOS development. In this article, we will journey from basic concepts to advanced techniques such as monads and function composition.
Fundamentals of the Functional Approach in Swift
At the core of functional programming lie several key principles:
- Pure functions — the result depends only on the input arguments, with no side effects.
- Data immutability — using
letinstead ofvar. - Functions as first-class objects — they can be passed, returned, and stored.
Let's look at a simple example of a pure function:
// Pure function: no side effectsfunc add(_ a: Int, _ b: Int) -> Int { return a + b}
// Usagelet result = add(3, 5) // 8print(result)Closures and Higher-Order Functions
Closures are self-contained blocks of functionality that can be passed around and used in code. They are the foundation for working with higher-order functions.
// Simple closurelet multiply = { (x: Int, y: Int) -> Int in return x * y}
let product = multiply(4, 7) // 28print(product)Swift provides powerful higher-order functions for working with collections:
map
map applies a transformation to each element of an array:
let numbers = [1, 2, 3, 4, 5]let squared = numbers.map { $0 * $0 }print(squared) // [1, 4, 9, 16, 25]filter
filter selects elements that satisfy a condition:
let evenNumbers = numbers.filter { $0 % 2 == 0 }print(evenNumbers) // [2, 4]reduce
reduce combines all elements into a single value:
let sum = numbers.reduce(0, +)print(sum) // 15Function Composition and Currying
Composition allows combining simple functions into more complex ones. Swift does not have a built-in composition operator, but it is easy to implement:
// Composition operatorprecedencegroup CompositionPrecedence { associativity: left higherThan: AssignmentPrecedence}
infix operator >>>: CompositionPrecedence
func >>> <A, B, C>(left: @escaping (A) -> B, right: @escaping (B) -> C) -> (A) -> C { return { right(left($0)) }}
// Usagefunc double(_ x: Int) -> Int { x * 2 }func toString(_ x: Int) -> String { "\\(x)" }
let doubleThenString = double >>> toStringprint(doubleThenString(5)) // "10"Currying is the transformation of a function that takes multiple arguments into a sequence of functions, each taking a single argument:
// Regular functionfunc add(_ a: Int, _ b: Int) -> Int { a + b }
// Curried versionfunc curriedAdd(_ a: Int) -> (Int) -> Int { return { b in a + b }}
let addFive = curriedAdd(5)print(addFive(3)) // 8Monads and Functors in Swift
In functional programming, a functor is a type that can be mapped over. A monad is a functor with an additional flatMap (or >>=) operation. The most well-known examples in Swift are Optional and Array.
Optional as a Monad
let optionalValue: Int? = 5
// map — applies a function if the value is not nillet doubled = optionalValue.map { $0 * 2 }print(doubled) // Optional(10)
// flatMap