Thinking Functionally in Swift
When I was first learning how to program I was taught object-oriented programming, and therefore, I learned how to think about software with an object-oriented mindset. One day, I was watching an interview with Alan Kay on YouTube in which he made a statement:
Lisp is the most important idea in computer science.
Of course, at this point I had to figure out why he would say such a thing. As I was exploring Lisp, I stumbled upon functional programming. I’d heard the term before but I never really understood it. After all, the definition for functional programming on Wikipedia states:
In computer science, functional programming is a programming paradigm — a style of building the structure and elements of computer programs — that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.
Okay, it’s a different paradigm than object-oriented programming. But mathematical functions? Do I need to learn Category Theory to use functional programming? How do I apply mathematical functions to my model objects and view controllers?
Attempting to learn functional programming through the object-oriented programming lens can feel very daunting. But I’d like to demystify functional programming a bit and show, using an example, how elegant functional programming is.
There are a lot of interesting reasons to learn functional programming. Among them:
- Functional programming helps reduce the amount of state in our code
- It makes our code more testable and easier to reason about
- It makes our code more composable
I won’t try to argue that our code should be “all functional all the time”, but I will argue that all programmers should learn functional programming. Even if you continue writing object-oriented code, learning functional programming can influence the way you think about your object-oriented code.
Throughout the remainder of this article, I’d like to give a lite introduction to functional programming using a real world example — parsing a server JSON response into a model.
Note: Swift 4.0 introduces the
Codable protocol to facilitate parsing JSON. This is still an instructive example since JSON parsing is something many mobile developers are intimately familiar with.
Real World Example: Parsing a JSON Response from a Server
As shown in the diagram above, parsing a JSON response from a server requires two transformations:
- From the raw HTTP response into a JSON representation of the data.
- From the JSON representation to the model.
Between steps one and two, we will be working with a JSON representation of the data. This JSON representation allows us to perform incremental transformations of the response from the server. By parsing into an intermediate JSON representation before the model, our code becomes more composable.
The way the JSON is represented is not essential to understanding the functional programming concepts in this article. But, for the sake of completeness let’s define it now.
If you read the docs for
JSONSerialization, you’ll notice one of the rules for valid JSON is:
All objects are instances of
Since there are a limited number of cases, this seems like a good situation to use an enum.
JSONObject type defined, we now know the type of our data before the first transformation, between the first and second transformations, and after the second transformation. They are
Model (We’ll define the model a little later.)
There is one core concept that is going to drive our thought process through this exercise. We’re going to think of each step in the process as a transformation independent of variables external to our function.
Let’s take a look at how we can apply this thought process to the two transformations outlined above.
Transformation 1: Data to a JSON Object
We will start by defining a new type
Deserialize that transforms
Data into a
JSONObject. In functional programming, types are defined by their method signature. To use this type we write a function that returns our new
There are a couple things about our
JSON() function that I’d like to highlight.
- The function returns a closure. Looking at this with an object-oriented mindset might look odd, but this is standard functional programming.
- Notice that the closure returned from
JSON()is completely independent to any state that might exist outside the function. This is in stark comparison to object-oriented programming where we’re so dependent on state.
Transformation 2: JSON Object to a Model
As we did in our first transformation, let’s define a new type that accepts a
JSONObject as a parameter and returns a model of type
typealias Decode<T> = (JSONObject?) -> (T?)
Note: We make the
Decode function generic so we can decode a wide variety of models.
Since we don’t have any models defined yet, this is all the boilerplate code we need to start creating models from JSON! The next step is to define our models along with a function that can parse the JSON.
Putting It All Together
Let’s derive a simple example to show what it looks like to interact with this code. For this example, we’re going to expect the server to send us information about a user. We’ll define our
User model and write a function,
decodeUser(), to perform the transformation from
The proof is in the pudding as they say, so let’s see what our functional code looks like:
let userJSON: [String: Any] = [
let userData = try JSONSerialization.data(withJSONObject: userJSON, options: )
let user = decodeUser()(JSON()(userData))
Comparing and contrasting with the object-oriented approach
With these two different approaches outlined, let’s explore the pros and cons of the functional approach.
- Our functional code is completely immutable — We’re not required to maintain any references or state within our functions. This makes our code easier to reason about.
- The functional code is pure — Given the same input it yields the same output every time.
- Pure functions make our code more testable — If we can expect the same output for a given input, we can outline all the edge cases and make sure our code is fully tested.
- Swift encourages the use of functional programming — There are a lot of functional programming concepts built into the language already. Things like optionals,
reduceare all functional concepts, so why not learn functional programming?!
- It is more composable — By breaking logical pieces of code into separate functions, we can begin to chain together multiple functions to achieve the specific level of granularity that our programs need.
- Fewer lines of code — Our functional approach to this problem comes out to roughly 50 lines of code. Meanwhile, the objected-oriented approach was roughly 80 lines of code.
- The code is difficult to read when looking through an object-oriented lens — Functional programming concepts and constructs can be difficult to wrap your head around initially.
- The syntax looks weird — In functional programming, naming makes much more sense if you use verbs. This is counterintuitive if you’re coming from an object-oriented background where nouns are typically used for naming. For example:
// Using verbs
let user = decodeUser()(JSON()(userData))
// vs. using nouns
let user = userDecoder()(JSONDecoder()(userData))
- The syntax looks weird, part II — I mean, seriously, what is all this business with the extra parentheses?
A Swift playground with the above code is available here.
If you’d like to learn more about functional programming, here are some resources that I’ve found particularly interesting/informative.