Functional Paradigms in Swift
After the surprise announcement last week of a new programming language for iOS and Mac development, a lot of developers have been geeking out over Swift. At Tack we’re no exception; most of us on the iOS side of the aisle have been filling our chat room with “Sweet!”, “What the heck?”, and “Dude, check this out!” in equal measure.
There are a lot of different takes on different interesting features of Swift. One interesting thing to notice is how it has a number of features of a Functional Language. It’s a sign of the times. Functional Programming has been gaining momentum for the last few years. A number of languages growing in popularity are either functional, or, like Swift, multi-paradigm: flexible enough to allow more than one programming style to solve different problems.
As you find your way around Swift, pay attention to the features that fit the functional paradigm. If you do, you’ll be better able to take advantage of the things a functional language does well, like handle concurrency better, express yourself in code more clearly and expressively, and easily generalize complex problems to create flexible, reusable solutions. If you’d like to find out more about what functional languages are about, a great (early release) book that we’ve been reading is called Functional Thinking. Here are some tips to keep in mind as you’re learning Swift.
Avoid Nil, Unless You Mean It
Most languages we’ve used have had the concept of a null reference, like Objective-C’s nil. They’ve also famously been called a Billion Dollar Mistake. The main problem with nil is that it is usually an exceptional condition. A variable contains, or a function returns, either something useful or nil. How are we supposed to interpret nil? Does it mean that there is no data? Does it mean there was an error? Languages usually don’t help us here. We’re left to clutter our code with defensive if statements, or forget to do that and track down strange errors later.
While this isn’t strictly a functional feature, often, functional paradigm languages have a way to explicitly state that some data can either contain some meaningful data or have no value. Just like Scala’s Option/Some/None, for instance, Swift has the concept of an optional value.
Author Edit: This seems like it was a bug in the REPL, not actually a language feature. Calling methods on uninitialized variables isn’t supported.
In fact, to say that a variable may not have a value, you must be explicit about this exceptional case and declare it as Optional with the ? syntax. Once you do this, you’re making a clear statement that anyone using this variable should deal with the case that it has no value.
Later, when dealing with the Optional value, developers can take advantage of explicit or implicit unwrapping of the Optional value or the Optional binding syntax, like this:
That looks a lot more like the if statement checks we’re used to in Objective-C or other languages, but the benefit here is that it’s explicitly the case that optionalValue may not have a value. Like other functional languages Swift also has pattern matching capabilities, although these are fairly rudimentary. You can use them to deal with optionals like this:
It’s a bit strange that Optional binding doesn’t work as well in switch statements as in if statements, and still requires explicit unwrapping in the where guard clause, but calling out the .None case is a win for readability. Note that having .None as the first case guarantees that the explicit unwrapping will not fail in the subsequent cases.
Again, Optionals aren’t necessarily a functional feature, but avoiding nil is becoming more popular as more functional languages coming on the scene, and Swift, too, comes out of the box with these powerful, expressive Optional values. Another way Swift helps us keep things more explicit and safe is by helping us get a handle on mutable state or avoid it altogether.
Avoid Mutable State
A necessary feature of a purely functional language is that it has no ‘side effects’; functions are supposed to return an answer based on arguments, but nothing in the system should be changed by executing a function.
This often strikes programmers new to functional programming paradigms as strange, because to them, the point of a program is to change, or “mutate”, some state somewhere and record it or show the new state to the user. This hints at some of the differences in the types of problems pure functional language programmers want to solve compared to “the rest of us”, but there are still benefits to avoiding mutable state in any system.
One of the most relevant benefits of having no mutable state in a function is that concurrency problems do not occur. No matter how many threads, or cores, or whatever, are running the code, there’s no chance that something gets the program in a bad state because state never changes. Writing code with no mutable state makes concurrency much easier to deal with no matter what paradigm you’re working in.
The Object-Oriented programming paradigm treats mutable state as a matter of responsibility. A well architected class exposes methods to allow objects of that type to have control over mutating their own state. It’s the object’s responsibility to mutate its state and to not allow mutation by other objects.
A multi-paradigm language, as opposed to a purely functional language, has both options available. Swift has a few ways to deal with state. The most basic is the choice between let and var. Using let to define a value means that you never expect that value to change. Wherever possible, try to use let instead of var. This is a small win to readability and communicating explicit design goals: if you declare something a var, you’re explicitly stating that you expect the value to change.
Of course for us non-academic, working programmers, it’s usually the case that we don’t know values up front. More interesting than immutable variables is the capability collections have to be mutable or immutable. Just like Objective-C has both mutable or immutable arrays and dictionary, Swift does too, and makes the syntax of dealing with them a lot nicer.
Immutable dictionaries, or those declared with a let, don’t allow changes to either their keys or values.
Immutable arrays don’t allow changes to their length, but do allow values already defined to be changed. Author Edit: As of Beta 3 changes Arrays declared with a let are fully immutable. When passing around these simple data structures, using the immutable versions can help make code more explicit and deterministic. Make sure you understand how Swift deals with copying collection types by reading up on Assignment and Copy Behavior for Collection Types in the Swift reference.
Swift also has Structures, which, as opposed to objects, are copied by value instead of reference. This is a possible way to avoid mutating state, but may be strange to Object-Oriented developers. Immutable variables and copying by value are some interesting things dealing with avoiding mutable state, but where the functional paradigm really gets traction on cutting down on mutation problems is with high order functions.
Use High Order Functions
Another necessary feature of a functional language is them making functions first class citizens. That means that not only is it possible to have functions attached to objects or structures or enums (or even inside of functions), it’s possible to have variables or constants that point to functions, to pass functions into other functions, and to return functions from functions. It’s not something languages like Java can do at all. Objective-C made great strides with block syntax, and has always had use of C functions, but Swift provides even more power and much clearer syntax for dealing with functions.
The point of this power and clarity is to attack problems at a higher conceptual level. Instead of thinking about looping through lists of data and performing some actions on each item, there is a subtle shift to using higher order functions of a collection to take action on each item one at a time. Let me make the difference more clear with an example. Suppose we wanted to loop through a list of numbers and find their square, or find out which were odd, or sum them up. We could use a for-in loop and build up some mutable local variables and do the work. The functional way, however, looks like this:
These examples are as terse as possible, using Swift’s trailing closure syntax, shorthand argument names, and implicit return syntactic sugar. But syntax is not the only interesting thing going on here. Take notice of a few things. First, we don’t have any mutable state to worry about. Instead of calculating an answer using temporary variables, we use a function to give us the answer directly and place these into constants.
Second, we use methods already on the collections to do the high level iteration work. These functions are:
- map, which produces a new collection with new values for each of the values in another collection
- filter, which returns only the values we want from a collection
- and reduce, which turns a collection of values into a single value
Using these high level collection functions, which are common to almost all functional languages in one guise or another, allows us to separate the problem of iteration, which is general, from the work of deriving the answer that we want in particular. The answer we want in particular is derived by passing a closure, but we could have passed a function reference too. The point is that the heavy lifting is done one item at a time by a small chunk of composable, reusable functionality that deals with solving a particular problem.
As another example, in the Swift book, around page 125, there’s an example of a list of scene titles from Shakespeare’s Romeo And Juliet. There is an example of a for-in loop walking through this list to decide if the scene falls into Act 1 or not. Here’s what that looks like:
And now here it is done in a functional way:
This actually has more lines of code than the procedural approach, just to show that lines of code and terse syntax isn’t the point. The benefits to using the functional approach here is that reduce handles the iteration part while the reusable function countIfAct1 handles the counting logic. Not much in the procedural approach is reusable. It also requires us to manage our own mutable state, where the functional approach does not.
By having countIfAct1 be a reusable chunk of functionality we can start to organize the code around certain bits of utility that are like this. Maybe those are in Objects, maybe they’re in function libraries. On the other side, by having reduce handle the iteration logic we hand off that requirement to the language and compiler. We could possibly gain some benefit from this in the future, like perhaps a performance benefit if reduce learned to take advantage of multiple cores (as a pie in the sky example).
Finally, here’s the same functional approach with a bit more readable implementation, taking advantage of Swift’s trailing closure syntax.
However you feel about the ternary operator, I find that to be a very clean, readable solution with the right balance of terseness and information density. As you’re learning Swift, try to solve problems of this type using high order functions instead of procedural code.
The Take Away
Functional programming paradigms are something you should pay attention to, because important advances in computer science are going to be available to languages that offer those paradigms. Of course there are still a lot of problems where the Object-oriented paradigm still makes sense to use. For instance, dealing with view code still works best in the OO paradigm we’re all used to. However there are a lot of problems dealing with lists of data, transforming data, deriving answers from data sets, and so forth where the functional paradigm really shines.
Languages like Swift that offer a multi-paradigm approach are ideal because they give you a number of tools for different types of problems. The biggest takeaway from this post is to explore the functional tools that Swift gives you. Get familiar with the functional paradigm in general and start looking at where you can improve how you solve problems, more flexibly, safely, and with cleaner code.