Donnerstag, 19. März 2015

Function wrapping fun in Swift

I was today looking for a convenient way to wrap table view updates with begin/end updates, and also with the probably well known CATransaction.setDisableActions which is sometimes necessary to turn the animations really off.

As an introduction, here is how this is done normally:
        
tableView.beginUpdates()
// code updating table view
tableView.endUpdates()

CATransaction.setDisableActions(true)
// code with animations actually disabled
CATransaction.setDisableActions(false)

If we need both, we have to write:
        
tableView.beginUpdates()
CATransaction.setDisableActions(true)
// code updating table view and animations actually disabled
CATransaction.setDisableActions(false)
tableView.endUpdates()

This is not terribly painful to do, but it looks like something that can be implemented nicely using functional programming. Let's see.

The most basic approach is to put our updates in a function, that will be passed to another function, which executes the surrounding code:

func wrapUpdates(function:()->()) {
    tableView.beginUpdates()
    function()
    tableView.endUpdates()
}

we would call it like this:
        
wrapUpdates({println("doing some table view updates")})

Same with disable actions...
        
func wrapDisableActions(function:()->()) {
    CATransaction.setDisableActions(true)
    function()
    CATransaction.setDisableActions(false)
}

wrapDisableActions({println("doing some table view updates")})

And for both of them combined:
func wrapUpdatesDisableActions(function:()->()) {
        tableView.beginUpdates()
        CATransaction.setDisableActions(true)
        function()
        CATransaction.setDisableActions(false)
        tableView.endUpdates()
}

wrapUpdatesDisableActions({println("doing some table view updates")})


This is not so bad, at least we avoid having to repeat code when we need this exact functionality. But you see we are, still, repeating code. Imagine that instead of 2 lines our wrappers would have a lot of code. Imagine also, that we need more wrappers, different combinations, maybe different ordering. We would need each time a new function with repeated code. The solution for this kind of problem would be to to have reusable wrapper functions, which we can combine arbitrarily. The keyword for this is composition.

How do we achieve it?

The basic idea, is to have functions that return functions, that we can then wrap (also called "decorate") with other functions, and so on and so on. We end with one "composed" function, which basically will call all the functions when we call it. Let's see it in the example.

We will now write wrapUpdates such that it returns a function:
        

func wrapUpdates(function:()->()) -> ()->() {
    return {
        tableView.beginUpdates()
        function()
        tableView.endUpdates()
    }
}

The signature is a bit unwieldy so we will create a typealias for our function.
        
typealias VoidFunction = ()->()

Now the signature is more readable:

func wrapUpdates(function: VoidFunction) -> VoidFunction {
       return {
        tableView.beginUpdates()
        function()
        tableView.endUpdates()
       }
}

Take note of the {} where we put the code in - this is the syntax to make the block of code a closure/function, which in this case doesn't accept any parameters or returns anything.

We do the same with disable actions function:

func wrapDisableActions(function: VoidFunction) -> VoidFunction {
       return { 
        CATransaction.setDisableActions(true)
        function()
        CATransaction.setDisableActions(false)
      }
}

We call like this:

wrapUpdates({println("doing some table view updates")})()

Note () at the end, to call the function.

Nothing really better yet, we have to type more and the code looks complicated. But the magic comes when we start combining the functions:

func wrapUpdatesDisableActions(function: VoidFunction) -> VoidFunction {
    return wrapDisableActions(
            wrapUpdates(
                function
            )
        )
}
wrapUpdatesDisableActions({println("doing some table view updates")})()
This is much nicer than the first example!


This is the basic idea behind function composition. But it's still looks like it could be expressed more concisely.

Here Swift's ability to declare custom operators comes handy. A quick lookup with Google gives us  a quite satisfactory result:

infix operator >>> { associativity left }
func >>> <A, B, C>(f: B -> C, g: A -> B) -> A -> C {
    return { x in f(g(x)) }
}

You can find in that page an explanation and a very intuitive example of using this function for string manipulation. We will continue with our table view updates. The main difference is that we pass around functions instead of strings.

Now our wrapUpdatesDisableActions can be transformed into this awesomeness:
        
let wrapUpdatesDisableActions = wrapDisableActions >>> wrapUpdates

The call looks the same as before:
        
wrapUpdates({println("doing some table view updates")})()

Imagine what can be achieved with other, more complex examples:

let composed1 = f1 >>> f2 >>> f3 >>> f4 >>> f5
let composed2 = f2 >>> f5 >>> f4

composed1()
composed2()
//etc. etc.

Quite nice!

Appendix

In order to make our utilities generic, we can put them in extensions:

extension CATransaction {
    class func wrapDisableActions(function: VoidFunction) -> VoidFunction {
        return {
            CATransaction.setDisableActions(true)
            function()
            CATransaction.setDisableActions(false)
        }
    }
}

extension UITableView {
    func wrapUpdates(function: VoidFunction) -> VoidFunction {
        return {
            beginUpdates()
            function()
            endUpdates()
        }
    }
    
    var wrapUpdatesDisableActions: VoidFunction -> VoidFunction {
        return wrapUpdates >>> CATransaction.wrapDisableActions
    }
}
Note the composing closure is now in a computed variable - this way we have access to self.

 Now everytime we want to do begin end updates with (really) disabled animations, we just have to:
tableView.wrapUpdatesDisableActions({
    //do table view updates
})()