Functional Programming: Understanding the Beauty of Custom Operators in Swift


I confess that as soon as talk turns to custom operators, especially ones of three characters in length, my eyes glaze over.

Well that is how things were until yesterday. Yesterday things started to become clear when I learnt (from Functional Programming in Swift) that these operators aren't fancy ways of hiding code to increase its opacity. They in fact increase the transparency of code. Once you understand what they do!

Are you sitting comfortably?

I'm going to begin with a practical example. Let's suppose we want the ability to add various styles to an NSAttributedString. As in the previous posts on functional programming we start with a typealias to define the function we'd like to return. (Note: This starting point is a staple part of the approach demonstrated in Eidhof et al.'s book.)
typealias Attributer = NSAttributedString -> NSAttributedString
It takes an NSAttributedString and returns an NSAttributedString. Now it's time to write the first function which will change the colour of an attributed string:
func color(color:UIColor) -> Attributer {
    return {(aString:NSAttributedString) in
        let mString = aString.mutableCopy()
        mString.addAttribute(NSForegroundColorAttributeName, value: color, range:  NSRange(location: 0, length: aString.length))
        return mString.copy() as! NSAttributedString
    }
    
}
The color() function returns a function that makes a mutable copy of the string it receives, adds the attribute and then returns it as an immutable copy. Fulfilling the requirement of taking an NSAttributedString and returning an NSAttributedString. To utilise this function we'd do the following:
let aString = NSAttributedString(string: "Hello Swift!")

let addColor = color(UIColor.brownColor())

addColor(aString)
You can copy this gist into a playground to see it working.

And there's more

We can do more things to attributed strings than simply change the colour, and so we might write functions to underline, highlight and so on.
func underline(style:NSUnderlineStyle) -> Attributer {
    return {aString in
        let mString = aString.mutableCopy()
        mString.addAttribute(NSUnderlineStyleAttributeName, value: style.rawValue, range:  NSRange(location: 0, length: aString.length))
        return mString.copy() as! NSAttributedString
    }
    
}

func highlight(color:UIColor) -> Attributer {
    return {(aString:NSAttributedString) in
        let mString = aString.mutableCopy()
        mString.addAttribute(NSBackgroundColorAttributeName, value: color, range:  NSRange(location: 0, length: aString.length))
        return mString.copy() as! NSAttributedString
    }
}
And these can be utilised in the same way as before:
let aString = NSAttributedString(string: "Hello Swift!")

let addUnderline = underline(NSUnderlineStyle.StyleSingle)
let addColor = color(UIColor.brownColor())
let addHighlight = highlight(UIColor.yellowColor())

addColor(aString)
addUnderline(aString)
addHighlight(aString)
Already this is a great simplification of the way we normally handle attributed strings (gist), but what if we want to add two styles at once not one? Well we could do this:
let aC = addColor(aString)
addUnderline(aC)
or perhaps this:
addUnderline(addColor(aString))
But imagine doing either of these with three or more attributes:
// this 
let aC = addColor(aString)
let aU = addUnderline(aC)
addHighlight(aU)

// or
addHighlight(addUnderline(addColor(aString)))
It works and it uses very little code but the first approach is cumbersome and the nesting (as with any nesting) is more and more difficult to understand as the levels increase.

Solution: Custom operator

The solution that functional programming supplies (via Functional Programming in Swift) is a custom operator that forwards one filter onto another in order to combine them. To do this we'll start by defining the operator:
infix operator >>> { associativity left }
It is "infix" because it comes between two values and its associativity is left so the left value is taken first and we can read left to right. (Note: for the function we're about to write, the result would be the same whether we used left or right associativity.)
func >>> (attributer1:Attributer, attributer2:Attributer) -> Attributer {
    return {aString in attributer1(attributer2(aString))}
        
}
As before the function that we return must take an NSAttributedString and return an NSAttributedString, but the function that we have created using a custom operator takes two functions of type Attributer and returns a function of type Attributer.

We could've written the function without a custom operator:
func attributeCombiner(attributer1:Attributer,attributer2:Attributer) -> Attributer {
    return {aString in attributer1(attributer2(aString))}
    
}
But the difference is in the implementation. For this latter function we'd write
let combo = attributeCombiner(color(UIColor.blueColor()), attributer2: highlight(UIColor.yellowColor()))

let aString = NSAttributedString(string: "Hello Swift!")
combo(aString)
Whereas for the version that employs the custom operator, we'd write this:
let combo = color(UIColor.blueColor()) >>> highlight(UIColor.yellowColor())

let aString = NSAttributedString(string: "Hello Swift!")
combo(aString)

Recursively better

The visible improvement between the custom operator and the straight function becomes even clearer as the operator is used repeatedly for combining what is potentially an endless number of attributes:
let combo = color(UIColor.blueColor()) >>> highlight(UIColor.yellowColor()) >>> underline(NSUnderlineStyle.StyleDouble)

let aString = NSAttributedString(string: "Hello Swift!")
combo(aString)
And often we will see this kind of thing broken onto separate lines for readability:
let combo = color(UIColor.blueColor())
    >>> highlight(UIColor.yellowColor())
    >>> underline(NSUnderlineStyle.StyleDouble)
Which makes the implementation even more appealing. And this is really a return to the beginning, because if I'd seen code like this (gist) the day before yesterday I would've glazed over. Yes it looks appealing I would say, but how on earth does it work, you're just hiding stuff from me.

Conclusion

In part my initial assumption is true, some complexity is hidden but the greater amount of complexity is never written. Paranoia should therefore be put to one side. Once we understand the meaning of custom operators and the type of function they are performing then we come to terms with their beauty being far more than skin deep (if you'll excuse the cliché).

I cannot thank the writers of Functional Programming in Swift enough for writing their book and for helping me to understand this powerful approach to programming. I'm only at the end of Chapter 3 but already I've learnt to understand so much (and my eyes will stay unglazed for far longer in the future). I'm looking forward to being taught even more uses for custom operators, and exploring further newfound logic as the book unfolds.

Note: I used the example of an attributed string in this post because I thought it appealing to see a range of styles applied in this way. However, following some debate on twitter, I understand it perhaps doesn't illustrate the idea of >>> forwarding from one function to the next as clearly as it might, and of order being important. This gist does a better job of doing that but the original example has the upper hand for its visual angle.

Endorse on Coderwall

Comments