Swift’s design focuses heavily on the penetration of functional thinking, which gives us a new direction to go in our daily development. Many people may not know much about functional programming, in fact, I did not have much contact with functional programming, so this article is just to talk about the convenience of functional programming to us, there are mistakes are welcome to point out.

There are a lot of libraries that use functional design ideas, such as the famous ReactiveX, which abstracts a sequence of things into a signal source, and you can look at the signal source, or you can dress it up with a transform or operator to get a new signal source, And this call can happen in a chain, and then we subscribe to the final source, and when the original source sends out a signal, that signal will go through the layers of transformation to get the data that you want to get to the observer.

This might be a little abstract, but for a simple example, we have a UITextField, and when the user enters, we take the value that the user enters, we hand it to a network request, we hand the result of the network request to a parsing function, and the result is displayed on a UILabel. The whole logic, if you do it the traditional way, will have all kinds of states, all kinds of events, and those states will be changed, and if the logic gets more complicated, the code will get very complicated, and this pattern is called imperative programming. Let’s see what happens when ReactiveX is combined functionally:

textField.rx_value
    .map(someTransformer)
    .flatMap(startNetworkRequest)
    .map(anotherTransformer)
    .bindTo(label.rx_value)
    .dispose(...)Copy the code

The above is just a pseudo-code, but the logic is still very clear, all the functions have no side effects, is relatively pure functional programming. This allows us to turn a complex logical flow into a clear flow of events that can be described in just a few lines of code. This pattern is declarative programming.

Getting Started

There’s so much up there, there’s nothing real. Let’s take a look at functional programming in action with an example.

But before we do that, let’s do a little testing. Suppose we have the following two arrays:

let numbers = [8, 2, 1, 0, 3]
let indexes = [2, 0, 3, 2, 4, 0, 1, 3, 2, 3, 3]Copy the code

Now we’re going to pull the numbers from numbers based on indexes and concatenate them into a string. If we use imperative programming, we can easily think of code like this:

var temp = [Int]()
for i in indexes {
    temp.append(numbers[i])
}

var result = ""
for n in temp {
    result += String(n)
}

print(result)Copy the code

OK, the code works, but I think it’s bad, and certainly not functional, because the logic is full of commands and state changes.

Let’s rewrite it using the function:

print(indexes.map({ "\(numbers[$0])" }).reduce("") { $0 + $1 })Copy the code

This is really functional, without any new intermediate values appearing, without any state changes, we’ve done all the work of the previous nine lines in one line. It is a good illustration of how map and Reduce, the heart of functional programming, work.

Map – Transforms an existing element, either numerically or by type, but the only thing that cannot be changed is the dimension of the output. That is, if the input is an integer, then the output must be of some type, not an array, because the map function doesn’t help you expand the array, syntactically, but the result is not what you want. The flatMap below may help you.

FlatMap – Very similar to a map, except this time, you can change the dimensions. Through the flatMap function, the input value will become another type that can be manipulated by the map (such as an array). Then each element of this type will be expanded and added to the result, meaning that the input may have three values. The output has more or less value, which is magical. Here’s an example:

print([1, 3, 2].flatMap { [Int](1...$0) })    // [1, 1, 2, 3, 1, 2]Copy the code

Each element in the input array produces a sequence of its own size, and the output is concatenated together.

Reduce — mathematical normalization, which is to convert a set of numbers into a number after a certain operation, usually we can use it to calculate the sum of a set of numbers, for example:

print([1, 2, 3, 4].reduce(0) { $0 + $1 })    // 10Copy the code

Reduce takes an initial value and a function in which you take the current element and the current sum, return a new sum, and so on, eventually return the last sum.

Filter – This is easier to understand, to filter the elements of an array by a function, nothing to say.

What’s Next?

Swift is a multi-paradigm programming language. Let’s combine protocol and generics to see how to implement chain operations in Swift.

Now suppose we have three transformations:

  • To exponentiate a number
  • Reduce an even number by incrementing it by one if it is not even
  • Turn a number into a string

If we use the conventional method, we will do this:

string(even(power(e, n)))Copy the code

That’s easy, but now what if I want to add a transformation? I’m going to have to modify the function call, which is obviously going to be a little bit messy.

Now let’s use functional thinking to reconstruct this example.

First we abstract the transformation with a protocol:

protocol Transformer {
    associatedtype InputType
    associatedtype OutputType
    func transform(elem: InputType) -> OutputType
}Copy the code

Then implement these transformations:

struct PowerTransformer : Transformer {
    let n: Int

    init(n: Int) {
        self.n = n
    }

    func transform(elem: T) -> T {
        return Int(pow(Double(elem as! Int), Double(n))) as! T
    }
}

struct EvenTransformer : Transformer {
    func transform(elem: T) -> T {
        return elem % 2 == 0 ? elem : elem + 1
    }
}

struct StringTransformer : Transformer {
    func transform(elem: T) -> String {
        return "\(elem)"
    }
}Copy the code

I’m using generics here, so if you’re not familiar with generics, check out the official Guide. Now the chain calculation is the same as before, but since we have the abstraction of the transformation, we can easily implement a combinatorial transformation type:

struct ComposedTransformer : Transformer {
    let transformer1: T
    let transformer2: U

    init(transformer1: T, transformer2: U) {
        self.transformer1 = transformer1
        self.transformer2 = transformer2
    }

    func transform(elem: T.InputType) -> U.OutputType {
        return transformer2.transform(transformer1.transform(elem))
    }
}Copy the code

It takes two transformations and calls them in turn, printing the final result. By recursive induction, we can keep combining, generating combinatorial transformations, and then combining that with other transformations… And then we get the final combinatorial transformation that we can use to compute various values.

Higher

But creating a composite transform now seems a bit cumbersome because of the long constructor arguments, and the whole statement is still the same as a nested function call. Don’t forget that Swift supports custom operators!

infix operator ~> { associativity left }
func ~>(t1: T, t2: U) -> ComposedTransformer {
    return ComposedTransformer(transformer1: t1, transformer2: t2)
}Copy the code

I’m sure you know how to use it. This operator helps us generate combinatorial transformations, so we just need to do this:

let powerTransformer = PowerTransformer(n: 3)
let evenTransformer = EvenTransformer()
let stringTransformer = StringTransformer()

let composedTransformer = powerTransformer ~> evenTransformer ~> stringTransformerCopy the code

I’ve got this chain of operations.

composedTransformer.transform(7)Copy the code

And since the operators are not nested, we can easily change the order of the chain of transformations, and we can add and delete transformations at will. Is it convenient?

Wrap Up

Of course, functional programming has far more advantages than that, and I’m just throwing out a few Pointers here. Session 419 – Protocol and Value Oriented Programming in UIKit Apps. Session 419 – Protocol and Value Oriented Programming in UIKit Apps Anything that involves value types can be associated with functions.

To sum up, functional programming is very good, very powerful, although it is an old programming idea, but modern programming ideas are also close to it, combined with new technology, the appropriate use of functional will certainly improve your development efficiency!