What is FP?
We know FP matters, but what is it really? There are a lot of discussions related to FP superiority and different definitions of it, but simply, it is a style of programming that models computations as the evaluation of expressions or declarations. FP is a declarative programming style, as opposed to OOP, which is categorized as imperative programming.
Theoretically, FP employs the concepts of category theory, which is a branch of mathematics. It is not necessary to know the category theory to be able to program functionally, but studying it will help to grasp some of the more advanced concepts, such as Functors, Applicative Functors, and Monads. We will get into category theory and its relationship with FP later, so for now, we are not going to talk math and we will scratch the surface of FP pragmatically.
Let's start with an example to understand the differences between imperative and declarative programming styles. The following example gives two different approaches to array-element multiplication:
let numbers = [9, 29, 19, 79]
// Imperative example
var tripledNumbers: [Int] = []
for number in numbers {
tripledNumbers.append(number * 3)
}
print(tripledNumbers)
// Declarative example
let tripledIntNumbers = numbers.map({ number in number * 3 })
print(tripledIntNumbers)
In the imperative example, we create a mutable array of integers. Then, we give a command to go through all items in the array, multiply each item by 3, and add it to our mutable array. Basically, we order the compiler to follow specific steps. As it is written in an imperative style, when we read it, we will need to trace and follow the same steps, like a compiler, which is not very intuitive.
In the declarative example, we declare how numbers should be mapped, multiplying each number by 3. It may not look that intuitive right away if we do not know the map function and closures, but for now we need to understand the differences between commanding and declaration. This example may not be enough to grasp it. Also, understanding a concept is one thing and applying it is another. That is why we will have more examples in upcoming sections and chapters. In fact, everything in this book will be written declaratively, so we will utilize the concept intuitively.
In FP, functions are the fundamental building blocks, so programs are structured by functions. In OOP, programs are composed of classes and objects. This is a fundamental difference because, in OOP, statements can mutate the state of objects when executed, as opposed to FP, which avoids using mutable states.
FP promises that avoiding mutable states makes it easier to test, read, and understand the code. Although it is a well-known fact that state management is hard and error-prone, it is not easy to avoid mutable states in some cases, such as cocoa development and file and database operations. For now, we consider traditional OOP and FP, and we will get into the comparison and combination of different paradigms in an upcoming chapter.
FP requires functions to be first class. First-class functions are going to be treated like any other values, and can be passed to other functions or returned as a result of a function.
FP requires functions to be able to be formed as higher-order functions that take other functions as their arguments. Higher-order functions can be used to refactor code, reduce the amount of repetition, and to implement domain-specific languages (DSL).
DSLs are languages that are specialized for a particular application domain. Domain-Specific Languages, a book by Martin Fowler, is a great reference for the curious. For more curious readers, declarative Auto Layout DSLs for Swift such as SnapKit and Carthography and a talk given by Rahul Malik at Functional Swift conference on writing domain specific languages (https://github.com/rahul-malik/writing-dsls) are great resources.
FP requires functions to be pure so they do not depend on any data outside of themselves and do not change any data outside of themselves. Pure functions provide the same result each time they are executed. This property of pure functions is called referential transparency, and makes it possible to conduct equational reasoning on the code.
Equational reasoning is a way to reason about our code. It enables us to replace code blocks with others without worrying about evaluation order or state. In OOP, generally, the value of a code block depends on object context or state. Having a context and depending on state prevents replacing a code block by another, and therefore makes it impossible to conduct equational reasoning.
First-class, higher-order, and pure functions empower us to compose our applications with functions instead of using classes and objects as building blocks.
In FP, expressions can be evaluated lazily. For instance, in the following code example, only the first element in the array is evaluated:
let oneToFour = [1, 2, 3, 4]
let firstNumber = oneToFour.lazy.map({ $0 * 3}).first!
print(firstNumber) // The result is going to be 3
The lazy keyword is used to get a lazy version of the collection. lazy is declared in Swift's Standard Library as a view onto the collection that provides lazy implementations of normally eager operations, such as map and filter. In this example, only the first item in the array is multiplied by 3 and the rest of the items are not mapped.
At this point, we should have a very broad view of FP concepts. To be able to get into the detail of these concepts, we will need to cover some of the Swift language basics, which are provided in the following section.