If you’ve programmed in Swift, then you’ve probably heard of functional programming and some ugly words like functors and monads. Here’s my take on teaching the world what all that means.
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. (Wikipedia)
In terms of Swift, functional programming means using
lets instead of
vars when dealing with data. This has its benefits, mainly that functional code is less prone to bugs and easier to understand than imperative code. Imperative programming is the opposite of functional programming — it’s a paradigm that uses statements that change a program’s state.
A key practice in functional programming is breaking code down into smaller, pure functions, which can be used several times throughout the project. Pure functions are functions that have no side effects on the program as a whole, meaning they exist solely to help other functions which do participate in the program.
I think the best approach to understanding how to implement functional programming is to see how imperative programming solves a problem, and then see how functional programming can solve that same problem.
I have an array of numbers, but I only want even numbers from that array.
Notice how we’re mutating a variable in order to aggregate an array of even numbers.
Before I show you the functional solution, I want to introduce you to Swift’s
.filter() method for arrays:
.filter() is called on an array and takes a function as a parameter (closure) and returns a new array of elements that satisfy a predicate declared in our closure. In other words, the closure takes an array element and returns a Boolean value, which is true or false based on a predicate we define in the closure.
Now, the functional solution to our problem:
…and we can make our implementation even shorter with Swift’s closure shorthand notation:
[2, 4], it was never once
[1, 2, 3, 4, 5].
We essentially turned 8 lines of code into 1, and made our code much easier to read and understand (not to mention now we’re only dealing with one constant instead of multiple variables.) I hope you’re beginning to see the elegance of functional programming.
But wait, there’s more…
.filter() isn’t the only method you can use on arrays. Swift has a few other cool methods out of the box that can help you code more functionally.
.map() : takes a value out of the array, applies a function to it, and then puts the result of that function back into a new array that gets returned to you.
.flatMap() : basically just like
.map(), except it filters out any
This can be especially useful when you’re trying to do something like converting an array of
Strings to an array of
.sorted() : returns an array sorted according to a given function with a predicate.
.forEach(): calls the given closure on each element in the sequence.
Behind the Scenes
Now that you’ve seen functional programming in practice, let’s try to think about making some of our own functional methods. First let’s take a look at Apple’s documentation of
What the heck is this function doing? Let’s break it down!
<T> : the function is using generics.
(_ transform: (Element) throws -> T) :
.map() is essentially taking a value named ‘transform’ of a function type (closure).
A function that uses a closure as a parameter is called a higher order function.
(Element) is an element in our array (every element is processed through an iteration in the implementation.)
The closure type
(Element) throws -> T is basically a “superclass” of the closure type
(Element) -> T, except that it can throw errors. But wait, doesn’t that mean we need a
try block in our
.map() implementation? No! That’s where the magic of
rethrows comes in!
rethrows : there’s not a lot of documentation on this little magic keyword, but here’s what it does (it’s so cool) — if you have a function that accepts a throwing closure as a parameter, then you can use
rethrows in the return type of that function in order to make the closure register as a throwing function to the compiler only if it actually throws an error. So, for example:
doSomething(closure:) will register as a throwing function only if
closure() throws an error.
-> [T] : finally,
.map() returns an array of new elements (which could be any type so if we wanted to convert an array of
Ints to an array of
.map() would return
So how does this help me program more… functionally?
If you take away anything from that breakdown of
.map(), understand that
.map() is basically taking a function as an argument and returning the result of that function. And that’s functional programming — using higher order functions to solve complex problems.
We can’t access Apple’s hidden method implementations, but here’s an implementation of my own simple functional method (using an extension.)
Functors and Monads
A functor is just a fancy word for something that can implement
.map(). So for example, in…
array is a functor.
A monad is simply a functor that can also implement
array is a monad and
newArray is a functor.
Hopefully I’ve helped demystify functional programming for you. Don’t hesitate to reach out to me on twitter if you have any questions.