Thoughts on Functional Programming in Swift
Like most of you, I have to use Objective-C at my day job. I could only craft my Swift skills at night. Swift is not a purely functional language. It can be use imperatively because all frameworks from Apple are written in Objective-C at the time of writing. However, it is also functional, learning from modern languages like Haskell, F#, etc. At the beginning when reading Swift blogs and tutorials, many people talked about terms like functors and monads, those sounded alien-like to me. I started to learn Haskell to understand what they were talking about. I’m not an expert, and still learning functional programming and Swift, but I wanted to share what I’ve learned so far. Hopefully it will save you some troubles to get into the functional programming world.
Key Concepts
Higher-order functions
One key concept of functional programming is higher-order functions. According to Wikipedia, a higher-order function:
- takes one or more functions as an input
- outputs a function
In Swift, functions are first-class citizens, like structs and classes, as we can see in the following example:
let
addFive = { $0 + 5 }
addFive(7)
// Output: 12
We define a function as an inline closure, and then assign it to an inline closure. Swift provides shorthand argument to it. Instead of by name, we can refer to the arguments by number, $0, $1 and so on.
func
addThreeAfter(f:
Int
->
Int
) ->
Int
->
Int
{
return
{ f($0) + 3 }
}
let
addEight = addThreeAfter(addFive)
addEight(7)
// Output: 15
The argument type Int -> Int
means it is a function that takes an Int as an argument, and returns an Int. If a function requires more than one arguments, for example, it has two Int argument, we can define the type as (Int, Int) -> Int
.
The return type of addThreeAfter
is also a function. It equivalents to func addThreeAfter(f: Int -> Int) -> (Int -> Int)
.
[addFive, addEight].
map
{ $0(7) }
// Output: [12 15]
map
is a special function for container type, such as Array, Optional. It unwraps values from the container type, then applies a transform for each, and wraps them again. We use map
here to pass 7 as an argument to each function in the array, and wraps the results into a new array.
In summary, we can assign functions to variables, store them in data structures, pass them as arguments to other functions, and even return them as the values from other functions.
Pure functions
A function is called a pure function if it has no observable side effects. But what the heck is side effect?
A function or expression is said to have a side effect if, in addition to returning a value, it also modifies some state or has an observable interaction with calling functions or the outside world.
— from Wikipedia
The two functions addFive
and addEight
are pure functions. They don’t modify the input value, or change any global state. In the example below, addFive2
modifies the input, so it is not a pure function.
func
addFive2(
inout
a:
Int
) ->
Int
{
a += 5
return
a
}
var
a = 7
addFive2(&a)
a
// Output: 12
Functions that access or modify a database, the local file system, or printing strings to the screen are also considered impure. Because they modify the state of the database records, file system, and display buffers respectively. Sometimes side effects are desirable. Without side effects, we could not interact with the program.
Haskell introduces types like IO to separate pure and impure layers. But in Swift, we don’t need to worry too much about this separation. We could still use Cocoa APIs as usual. But, I strongly encourage the use of pure functions whenever possible. With pure functions, the code is more predictable. A pure function will always return the same value if given the same input. It’s easy to test, too. We don’t need to mock a data structure to satisfy its internal state requirements to test a function.
Imperative & Functional Programming
All above are very theoretical. You may want to know how functional programming with help us solve problems in a better, clearer, or less error-prone way. What are the benefits we could gain from it?
First, you need to understand that we could do almost anything in imperative programming, and functional programming does not extend the possibilities of what we could do.
Second, if you come from the imperative programming world, some functional code is harder to understand at first, especially those with custom operators. It is not because functional programming is hard and obscure, but it’s because our mindsets are trained from years of imperative programming practices.
Take a look at this example of imperative code, which we can rewrite as functional code. These two do exactly the same things.
// Imperative
var
source = [1, 3, 5, 7, 9]
var
result = [
Int
]()
for
i
in
source {
let
timesTwo = i * 2
if
timesTwo > 5 && timesTwo < 15 {
result.append(timesTwo)
}
}
result
// Output: [6, 10, 14]
// Functional
let
result2 = source.
map
{ $0 * 2 }
.
filter
{ $0 > 5 && $0 < 15 }
result2
// Output: [6, 10, 14]
It is arguable which one is clearer. But from this simple example you can see the main difference between imperative and functional programming. In imperative programming, we instruct the computer how to do something:
- Iterate through
source
- Get the result from the element, and multiply by 2
- Compare it with 5 and 15
- If it is bigger than 5 and less than 15, put it into
result
However, in functional programming, we describe what to do:
- Transform each element in
source
to itself multiplied by 2 - Only select the ones with value bigger than 5 and less than 15
I’m not going to persuade you functional programming is better. It’s your personal preference. After all, good code is all about writing code that:
- Works as intended
- Is clear to you and your team
An Example: Reverse Polish Notation Calculator
I like Swift and functional programming, because it enables me to solve a problem in a different perspective. There is usually more than one way to solve a problem. Exploring a better solution helps us grow to become good developers.
Let me show you a functional example. It is a calculator for algebraic expressions of reverse polish notation, or RPN in short. (It is a Swift implementation of the Haskell example in Learn You a Haskell for Great Good.)
A RPN expression of (3 + 5) 2 is 3 5 + 2 . You may think of this as a stack of numbers. We go through the RPN expression from left to right. When encountering a number, we push it onto the stack. When we encounter an operator, we pop two numbers from the stack, use the operator with those two numbers, and then push the result back onto the stack. When reaching the end of the expression, the only one number left on the stack is the result (assuming the RPN expression is valid). For more explanation about RPN, please check on Wikipedia.
We want a function that returns the result for an RPN expression.
func
solveRPN(expression:
String
) ->
Double
{
// Process the expression and return the result
}
Given a RPN expression String “3 5 + 2 *”, first we need to transform it into an array of elements that we can process. There are two kind of elements, operand and operator. The Enum data type in Swift comes in handy for defining the element. We name it RPNElement
.
enum
RPNElement
{
case
Operand
(
Double
)
case
Operator
(
String
, (
Double
,
Double
) ->
Double
)
}
Next, we split the expression into an array of Strings, then map
it into an RPNElement array.
extension
String
{
func
toRPNElement() ->
RPNElement
{
switch
self
{
case
"*"
:
return
.
Operator
(
self
, { $0 * $1 })
case
"+"
:
return
.
Operator
(
self
, { $0 + $1 })
case
"-"
:
return
.
Operator
(
self
, { $0 - $1 })
default
:
return
.
Operand
(
Double
(
self
.toInt()!))
}
}
}
func
stringToRPNElement(s:
String
) ->
RPNElement
{
return
s.toRPNElement()
}
func
solveRPN(expression:
String
) ->
Double
{
let
elements = expression.componentsSeparatedByString(
" "
).
map
(stringToRPNElement)
// Further process
}
Next, we will go through the array and process it according to how RPN works, as I described earlier. We reduce
the array into a Double array. Assuming the expression is valid, the Double array should only contain one element. It will be the result we want.
func
solveRPN(expression:
String
) ->
Double
{
let
elements = expression.componentsSeparatedByString(
" "
).
map
(stringToRPNElement)
let
results = elements.reduce([]) { (acc: [
Double
], e:
RPNElement
) -> [
Double
]
in
switch
e {
case
.
Operand
(
let
operand):
return
[operand] + acc
case
.
Operator
(
let
op,
let
f):
let
r = f(acc[0], acc[1])
return
[r] + acc[2..<acc.count]
}
}
return
results.first ?? 0
}
solveRPN(
"3 5 + 2 *"
)
// Output: 16
Where to Go From Here?
If you are interested in learning more about functional programming, I highly recommend the following two books:
- Functional Programming in Swift by Chris Eidhof, Florian Kugler, and Wouter Swierstra
- Maybe Haskell from thoughtbot
Even though the second book is written for Haskell, but the concepts also apply to Optional in Swift as well. Besides, it explains Functor, Applicative, Monad in details.
Look at the line case Operator(String, (Double, Double) -> Double)
I’m not sure why the string value of the operator is stored. It is not referred to at any point. It gets assigned to a variable in this statement, but is not used:
case .Operator(let op, let f):
let r = f(acc[0], acc[1])
return [r] + acc[2..<acc.count]
}
I tested it in a playground and it worked fine, then removed the String from the enum and modified all the places that then put xcode in a tizz, and it worked again.
Good tutorial, btw.
Earlier in the tutorial you have:
A RPN expression of (3 + 5) 2 is 3 5 + 2
The “3 5 + 2” is a typo and should be replaced by “3 5 + 2 *” what you have later in the tutorial
This implementation appears to be incorrect.
I believe this line
let r = f(acc[0], acc[1])
should be
let r = f(acc[1], acc[0])
because currently “3 5 -” evaluates to 2 where it should be -2. or is my understanding of RPN wrong?