Functional Programming

Swift Basics: reduce()

By the way, this post is also available as an interactive Swift Playground. 🤓

The reduce function is super useful for when you need to compute (or reduce 😏) a set of values down to a single value.

Let's take a look at a simple example. Say we wish to sum an array of numbers to find the total. Without the reduce function, we might use a foreach loop:

let valuesToReduce: [Int] = [1,2,3,4]
var total: Int = 0

valuesToReduce.forEach { value in
    total += value
}

total // total: 10

However, in Swift we try to avoid mutability.

Here is the same calculation written using reduce. Take a look then we'll break it down:

// use `reduce` to sum each Int in an array, resulting an Int:
let total = valuesToReduce.reduce(0) {
    (accumulation: Int, nextValue: Int) -> Int in
    return accumulation + nextValue
}

total // 10

or written more simply as:

let total = valuesToReduce.reduce(0) { $0 + $1}
total // 10

Note that the closure we to reduce is called once for every value of the array.

The zero that we pass in as a parameter is called the accumulator, that is: the starting value upon which successive calls of reduce function will be applied, effectively accumulating our final result. Actually, given that we're using value types and 0 is immutable, we will not be mutating the zero - we're really passing the return value from the first call (i.e. 0 + 1 = 1 - a new value) back in as the starting value (accumulator) of the next call.

We will see at the end that we can also use a mutable object as the accumulator and literally build up our object with each successive pass - which is sometimes useful for working with Cocoa.

Creating An Array from an Array:

The output of reduce can really be anything - even another Array. As a second example, let's use reduce to create a handy deduplication function which we can add as an extension on Array type.

The first thing to decide is what our starting value for this shall be. Our algorithm will be as follows: "start with an empty array and append each item, so long as that item isn't already present in the array". By this logic we shall finish with an array of distinct unique values.

Thus, we should start with an empty array of type <Element> (the Generic placeholder for the type of value the array can hold): [Element]():

extension Array where Element: Equatable{
    func deduplicate() -> [Element]{
        return reduce([Element]()) { (accumulation: [Element], find: Element) -> [Element] in
            guard accumulation.indexOf(find) == nil else {return accumulation}
            return accumulation + [find]
        }
    }
}

let result = ["Amsterdam", "Berlin", "Paris", "Amsterdam"].deduplicate() // -> ["Amsterdam", "Berlin", "Paris"]

Whoa whoa, slow down:

So:

extension Array where Element: Equatable - we should constrain this extension to be available only when the Array holds values that are equatable. Otherwise, how could we identify duplicates?

func deduplicate() -> [Element]{ - we're returning an Array of the elements of the same type that the starting array holds.

reduce([Element]()) { (accumulation: [Element], find: Element) -> [Element] in - [Element]() is what we start with - an empty array. This is the Accumulator that we'll be using. The signature of the closure we're passing to reduce has the accumulation that we're building up accumulation: [Element], and find: Element, the current element that we want to find.

guard accumulation.indexOf(find) == nil else {return accumulation} - if the element we wish to find is already in the accumulation, then exit early from this iteration, returning an unchanged accumulation value. This is the escense of deduplication.

return accumulation + [find] - otherwise, return the accumulation with find appended.

In the next example, we'll show that reduce can also be called on a Dictionary type, and that the accumulator can also be mutable if need-be:

Using a Mutable accumulator instead:

Say we want to take a Dictionary of type [String : NSURL], that is, a mapping of Town Name to Map URL, and turn it into an NSAttributedString of tappable (once added to a UILabel) linked text.

i.e. this:

let placeURLPairs: [String : NSURL] = [
    "Amsterdam" : NSURL(string: "https://www.google.nl/maps/place/Amsterdam/")!,
    "Newcastle" : NSURL(string: "https://www.google.nl/maps/place/Newcastle/")!,
    "Manchester" : NSURL(string: "https://www.google.nl/maps/place/Manchester/")!
]

to this:

We can, naturally, use a reduce function to acheive this. However, the API for building up NSAttributedStrings, is quite suited to using an NSMutableAttributedString and applying successive attributes and String values on it.

It's easy to tweak our reduce function to, instead of returning a new value each time, instead return a mutated version of the passed in accumulator, so this same object will be passed into each iteration and then will be the result by the end:

func attributedString(places placeURLPairs: [String: NSURL]) -> NSAttributedString{

    return placeURLPairs.reduce(NSMutableAttributedString()) {
        (accumulator: NSMutableAttributedString, placeURLPairs: (String, NSURL)) -> NSMutableAttributedString in

        // unpack the name and the URL:
        let (placeName, url) = placeURLPairs

        // Create the local attributed String which we'll append
        let localAttributedString = NSMutableAttributedString(string: placeName + " ")

        // Add a link to this name
        localAttributedString.addAttribute(
            NSLinkAttributeName, value: url, range: NSRange(location: 0, length: placeName.characters.count)
        )

        // Add this attributed string onto the mutable Accumulator
        accumulator.appendAttributedString(localAttributedString)

        // Pass the accumulator back out again
        return accumulator
    }
}


let placeURLPairs: [String : NSURL] = [
    "Amsterdam" : NSURL(string: "https://www.google.nl/maps/place/Amsterdam/")!,
    "Newcastle" : NSURL(string: "https://www.google.nl/maps/place/Newcastle/")!,
    "Manchester" : NSURL(string: "https://www.google.nl/maps/place/Manchester/")!
]

let attributedLinkedTownsString = attributedString(places: placeURLPairs)

let label = UILabel(frame: CGRect(x: 0, y: 0, width: 1000, height: 200))
label.attributedText = attributedLinkedTownsString

Hopefully this was useful, do let me know in the comments if there's any feedback or comments.

By the way, this post is also available as an interactive Swift Playground. 🤓

Notes on ReactiveCocoa 3.0 RC

I'll be honest - I did have real problems getting back to grips with ReactiveCocoa 3.0 since their API overhaul (despite being reasonably familiar with v2 - I even gave a talk on it). I'm getting there now (tacks.cc is written extensively with ReactiveCocoa 3.0 RC), and I'll try to update this post with tidbits from what I've learned because, to-date - even though the RAC codebase is well documented, function to function - there's not enough examples out there for how you actually use this stuff as a whole. Most of the example code that exists is still in Objective-C and generally relates to previous versions of RAC. There are some great blog posts (linked at the bottom), which really helped me figure it out.

Creating a signal

We can create a "pipe" which returns a Tuple pair: the Signal itself, and an 'observer' which we can use to manually send events, - events which pop out of the created signal. They go together.

let (self.locationSignal, self.locationObserver) = Signal<CLLocation, NoError>.pipe()

Signal of empty Events:

If you want a Signal that sends empty Next events, create your signal like so: Signal<Void, NoError>, and just send it Voids: .Next(Void)

You don't always need a real Error type

Signals rarely fail, so it's usually easier just to model them using NoError.

let locationSignal: Signal<CLLocation, NoError>

Using next: to start a new signal after completion - this is gone

In RAC 2, we could use next: to start a new signal once another completes. We don't this anymore - instead we concatenate signals, for example: localFetchProducer |> concat(networkFetchProducer)

Create a SignalProducer from a value

let individualComicProducer = SignalProducer<Comic, NSError>(values:comics)

Resources: