2. Functional Swift
Functional Programming in the Swift Language
This is a follow up to the previous post
1. Learn Swift by running Scripts
Look at the last example from the previous article
func square(x: Int) -> Int {
return x * x
}var num: Int? = nilif let newnum = num {
print(square(newnum))
} else {
print(“num is nil”) // prints - num is nill
}
Now try this
func square(x: Int) -> Int {
return x * x
}var num: Int? = nilprint(num.map(square)) // prints nilnum = 10print(num.map(square)) // prints Optional(100)
Wow! What was that? Take a closer look
num.map(square)
We called the map method of an optional type, with one argument, the function square! And map returned nil when num was nil. Try it with a non nil value.
When num is not nil, it calls the square function which was passed to it and returns the square of num! It says Optional(100) because the map function returns an optional.
The key here is that we passed the square function to the map method. When we define functions, we have to specify the parameter Types. So how do we specify the parameter Types? We know that the function square has the following Type.
(Int) -> Int
Functions as arguments
In the Swift language you can pass a function as an argument to another function. Here is an example. A function called each, which iterates over an array but does not return anything. This is not a built-in function.
func each(array: [Int], callback: (Int, Int) -> Void) {
for i in 0..<array.count {
callback(i, array[i])
}
}
The Type of the each function is
([Int], (Int, Int) -> Void) -> Void
each is a function that takes two arguments, an array of type integer
[Int] // This is one way to specify Array Types
and a callback function of type
(Int, Int) -> Void
and each returns
Void
The callback function takes two integers as its arguments and returns Void. The callback is called for every element of the given array, with the first argument being the Array index, and the second argument the array element.
Now let us try the each function by calling it with an array and a callback function. The callback is called printValue and it just prints the index and value to the console.
func printValue(index: Int, element: Int) {
print("index = \(index), Element = \(element)")
}func each(array: [Int], callback: (Int, Int) -> Void) {
for i in 0..<array.count {
callback(i, array[i])
}
}each([1, 2, 3, 4, 5],callback: printValue)
And here is the output to the console.
index = 0, Element = 1
index = 1, Element = 2
index = 2, Element = 3
index = 3, Element = 4
index = 4, Element = 5
Notice how string interpolation is done inside strings using ‘\()’ construct. You can use any expression that evaluates to a string inside the construct.
Generic Functions
Our each function has a serious problem! It can only handle arrays of type integer. We want each to be generic so that we don’t have to rewrite each for every other possible Type. And we can do just that with Swift.
func printValue(index: Int, element: Int) {
print("index = \(index), Element = \(element)")
}func each<T>(array: [T], callback: (Int, T) -> Void) {
for i in 0..<array.count {
callback(i, array[i])
}
}each([1, 2, 3, 4, 5],callback: printValue)
Look at the each function name now.
each<T>
This indicates to the compiler that each is a generic function, that uses a variable of Type T in its definition.
each<T>(array: [T], callback: (Int, T) -> Void)
T is a placeholder for the actual Type that will be used with each. The Swift compiler will ignore the Type for variable of T.
The array type is [T] and the callback type is (Int, T) -> Void.
Just to make sure try each with an array of Strings.
func printValue(index: Int, element: String) {
print(element)
}func each<T>(array: [T], callback: (Int, T) -> Void) {
for i in 0..<array.count {
callback(i, array[i])
}
}each(["Red", "Green", "Blue"],callback: printValue)
printValue just prints the given element to the console.
The map function for arrays
The function map takes an Array and a callback function as its arguments and returns a new Array. The callback is called for each element with the index and element, and the return value is appended to the new array.
func map<T, U>(array: [T], callback: (T) -> U) -> [U] {
var newarray: [U] = []
for i in 0..<array.count {
newarray.append(callback(array[i]))
}
return newarray
}let mappedArray = map([1, 2, 3, 4, 5]) {$0 * $0}
mappedArray will have the array [1, 4, 9, 16, 25] after this program runs. As an exercise verify this by using our earlier each and printValue functions.
We use a let statement here instead of a var statement. The let statement creates a constant identifier. In this case mappedArray is a constant. ie. you cannot modify its value unlike a variable created using the var statement.
Note that the source of this map function is not the actual built-in source. The built-in map is more intricate, It not only takes Array, but also Collections and Sequences.
Functional programming features supported by Swift.
- Higher Order Function — Functions can be passed as arguments to other functions, and can be returned from other functions.
- First Class Citizens — Functions can be treated like any other value in the language (first class citizen). You can assign them to variables, pass them as arguments and return them from functions.
- Nested Functions — You can define functions within a functions.
- Closures — A Closure is a special case of a function. You can pass arguments to it, and return values from it. Unlike functions, it does not have a name. It is more like an anonymous function or lambda in other languages. Though I suspect the nature of Closures in Swift is more akin to blocks in Objective-C.
- Anonymous Functions — They are just Closures
Functional programming features not supported in Swift
- Tail Call Optimisation. TCO is supported under certain circumstances though. Under maximum optimisation if ARC allows.
- Pure Functions
- Recursion — You can actually do recursion in Swift. However since there is no tail call optimisation, it is risky to use recursion. But you can safely use recursion if you are sure the depth of recursion is not in the magnitude of tens of thousands. Eg. you could use recursion for traversing tree structures like DOM, XML, JSON.
Learn More Functional Swift in the next post.