Monads Everywhere: Porting C#’s Tasks to Swift

In this talk, Nevyn Bengtsson takes a functional look at how to manage asynchrony in Swift. Drawing on his experience of porting over the Task class from C# by way of Objective-C, Nevyn explains the Swift version of a Task, and in the process, rediscovers monads!


Introduction (0:00)

Hi, I’m Nevyn! I usually do my stupid hacking using the Objective-C runtime, as I’ve done for years at Spotify, and now for a few years at Lookback. Recently, I’ve been getting into Swift, and I’m really enjoying it.

One of my favorite pieces of Objective-C is an open source library I wrote called SPAsync, a light-weight promises and async primatives library. As I ported it to Swift, I found some interesting stuff I’d love to show you.

Concepts (0:53)

First, let’s look at some concepts. One of my least favorite code smells is deeply nested scopes, and in particular, nested callbacks.

💩.fetchFromNetwork(input) { intermediate in
    💩.parseResponse(intermediate) { again in
        dispatch_async(dispatch_get_main_queue()) {
            updateUI(output)
        }
    }
}

This kind of code has three major problems areas:

  • Error Handling - If you have dual callbacks for errors, you’re back to the if-success-else-error pattern, where error handling is as far away from the work you’re doing as possible. It’s very difficult to read and follow. The more we nest the callbacks, the worse this error becomes.
💩fetchFromNetwork(input, callback: { intermediate in 
    💩.parseResponse(intermediate, callback: { again in
        dispatch_async(dispatch_get_main_queue()) {
            updateUI(output)
        }
    }, errback: { error in
        displayError("when parsing response, ", error)
    })
}, errback: { error in
    // This error is VERY far away from fetchFromNetwork!
    displayError("when fetching from network, ", error)
})
  • Cancellation - If you don’t abstract asynchrony in a good way, every API is going to have a different way of canceling. You’re also going to have to keep track of it yourself if you’re canceling in between tasks, which is terrible.
var cancelled = false
block var cancellable : Cancellable?
let operation = 💩.fetchFromNetwork(input, callback: { intermediate in
    if(cancelled) return
    cancellable = 💩.parseResponse(intermediate, callback: { again in
        if(cancelled) return
        ...
    })
})
func cancel() {
    cancelled = true
    operation.cancel()
    cancellable?.stopOperation()
}
  • Dependencies - If you have task A and B, and then you have a third task C that depends on the result of the first two before it can execute, you need to keep track of it all. With cancellation and error handling, this gets really messy.

In short, this is an area that needs abstraction. We want a single concept that abstracts the four concepts of a-value-in-the-future, error handling, cancellation, and dependencies between asynchronous tasks.

Is this just GCD? (2:35)

This may sound like I’m trying to describe GCD or NSOperationQueue. Those can be used for those tasks, but I don’t find them to be a really good match. They are both tools for concurrency, not asynchrony. They can make stuff run at the same time, but not necessarily stuff that will happen at different times. In particular, NSOperation does not abstract the concept of “value in the future”, only the other three things. While you can use it to fix these problems I’ve talked about, it remains awkward.

Apple seems to be encouraging people to use NSOperation, but if it’s truly such a great concept, why doesn’t every iOS API return an NSOperation for any asynchronous task? It’s just not a very convenient abstraction.

Third Party Libraries to the Rescue (3:30)

Some third party libraries have stepped in, such as ReactiveCocoa (RAC). RAC is a great library that does abstract all of these four concepts and a lot of other things. It’s powerful, but it’s also a big library and it is a separate programming paradigm you have to get into. It might not fit well with your app, or you may simply not want to write something like that. The cool thing about RAC is that it’s originally inspired by reactive extensions to C# and .NET.

If you take a look at the C# environment, you find some other nice libraries for doing asynchrony, like the Task class. In other environments, a Task is called a “promise” or a “future”, and it’s a really nice way to design the asynchrony in your application. It’s composable, and it’s really nice to build with.

To me, this is the missing component of iOS development. Apple gives us KVO, which is a great way of abstracting value changes anywhere throughout the iOS frameworks for your own code. The RAC people are able to build powerful stuff on top of UIKit because of KVO. If Apple incorporated promises into the standard libraries, we could have a way to abstract callbacks altogether throughout the platform, and we could build great tools on top of it. But they don’t, so I gave it a try!

Porting Task to Swift (5:09)

I tried porting C#’s version of future, which is called Task, to Objective-C and then to Swift. I’ll walk you through what I did and what I found.

class TaskCompletionSource<T> {
    public let task: Task<T>
    func completeWithValue(value: T)
    func failWithError(error: NSError!)
}

First off, there’s a really boring factory API that serves as a way to separate the producer and the consumer of an asynchronous operation. The producer would have a TaskCompletionSource, which is basically a factory for a Task so you can complete it or fail it.

On the other side, you have the actual Task, which is the thing that the consumer of something asynchronous uses. The Task is generic over the type T so it could wrap any kind of value that you will get in the future. It could have an accessor to get this value, but what would you do if the task hasn’t completed yet? Would you return a nil? Would you block? It’s kind of tricky.

Instead, there’s just an addCallback function that you call with your callback to get called when the value is there. You can also give it a queue, so that you can get that callback on whatever thread you want to. You can also add an error callback to respond to errors and to finally clean up after yourself whether you got an error or a value.

class Task<T> {
    public func addCallback(
        on queue: dispatch_queue_t,
        callback: (T -> Void)
    ) -> Self
    public func addErrorCallback(
        on queue: dispatch_queue_t,
        callback: (NSError! -> Void)
    ) -> Self
    public func addFinallyCallback(
        on queue: dispatch_queue_t,
        callback: (Bool -> Void)
    ) -> Self
}

If I stopped here, imagine how nice that would be: a single generic way of adding callbacks to any asynchronous task anywhere, and even multiple callbacks if you need it! But there is more.

// Two of these three are executed immediately after each other
network.fetch(resource).addCallback { json in
    let modelObject = parse(json)
    updateUI(modelObject)
}.addErrback { error in
    displayDialog(error)
}.addFinally { cancelled in
    if !cancelled {
        viewController.dismiss()
    }
}

Above is an example where some sort of network object is fetching a resource. I add a callback, and I’m not giving a queue, so it’s on the main queue. I parse the model and update the UI. Then, if there’s an error instead, I can just chain these and these callbacks will all be added to the same task object. So, I have an error callback for if there’s an error. Finally, you can see where I clean up the UI.

Chaining Tasks and More (7:21)

Next up is a way of expressing dependencies between tasks. This is where the type of signatures get a little bit hairy.

class Task<T> {
    public func then<T2>(on queue:dispatch_queue_t, worker: (T -> T2)) -> Task<T2>
    public func then<T2>(chainer: (T -> Task<T2>)) -> Task<T2>
}

Here is a then function, with two variants. One is if you have a block where you want to provide the work that you will execute, and you can give a queue on which you want to execute it. The other one is if you want to provide a task that you want to chain together with the first. I’ll show you how these work.

// A: inline background parsing on _worker_queue
func parse<T>(json) -> T
network.fetch(resource)
    .then(on: _worker_queue) { json in
        // First this function runs, running parse on _worker_queue...
        return parse<MyModel>(json)
    }.addCallback { modelObject in
        // ... and when it's done, this function runs on main
        updateUI(modelObject)
    }.addErrorCallback { ... }

// B: background parsing on Parser's own thread with async method
class Parser {
    func parse<T>(json) -> Task<T>
}
network.fetch(resource)
    .then(_parser.parse) // parser is responsible for doing async work on its own
    .addCallback(updateUI) // and then updateUI is called with the model object
    .addErrorCallback(displayError)

We fetch a network resource, parse it, and then update the UI. In scenario A, we have a synchronous parse function. It’s slow, so we’re calling then with a background queue. then returns a brand new Task, which we add a callback to, that is called once we’re done parsing the model. We can then update the UI with it.

In scenario B, parse already returns a Task because it itself has an internal worker queue. We can just give it to then, and then will give us a new Task that represents first fetching the network resource, then parsing it with the parser. We add a callback to it that updates the UI.

I’m also showing a really nice thing that Swift does that Objective-C doesn’t. Since function pointers, method pointers, and blocks are the same thing in Swift, we can just give it an existing method instead of creating an anonymous block. I like it.

Some Niceties (9:18)

class Task<T> {
    public func cancel()
    static func awaitAll(tasks: [Task]) -> Task<[Any]>
}

Finally, there’s cancel and awaitAll, which I just want to show you for the sake of completeness. In this example, it fetches a bunch of stuff off of the network, parses it as images, then updates the UI. It’s doing a lot of downloading and parsing, but all of these tasks are still represented by one single parent task. With cancel, we can cancel a whole chain of tasks with a single call. awaitAll lets us wait for the value of several tasks in one go. This is something you can do with operations or RAC as well, but it’s nice to do it with this small, tiny, class and still have that power of abstraction.

The Fun Part: Task.wrap() (9:58)

Now that we have the basic functionality down, let’s look at the really fun stuff that happens once you try to use the Swift type system to generically wrap existing callback-based APIs. SPTask contains Task.wrap, which takes a callback-based function and returns a task-based function.

class NSURLConnection {
    func sendAsynchronousRequest(
        request: NSURLRequest,
        queue: NSOperationQueue,
        completionHandler: (NSURLResponse?, NSError?) -> Void
    )
}

extension NSURLConnection {
    // : (NSURLRequest, queue) -> Task<NSURLResponse?>
    let asyncTaskRequest = Task.wrap(NSURLConnection.sendAsynchronousRequest)
}

NSURLConnection.asyncTaskRequest(myRequest, mainQueue)
    .then(_parser.parse)
    .then(_db.store)
    .then(_ui.update)

For example, say we have NSURLConnection sendAsynchronousRequest, which is a normal iOS API that takes a callback. I really want to integrate this into my task-based flow, but I don’t want to write a lot of boilerplate. I can automatically wrap it. asyncTaskRequest is a new method where I’m wrapping sendAsynchronousRequest. It’s actually a constant of the function point of type, which is a function that takes two parameters and returns the task. It’s almost the same as the first, but replaces the completionHandler with a Task. Then, I can just chain this together with everything else. I send NSURLConnection.asyncTaskRequest with my request, then I can parse it and store it in a database, and finally update the UI. I can also add a single error handler to that, or cancel it all with one call.

I’m used to doing this kind of stuff in Objective-C using runtime hackery, but that loses all type safety. Sometimes, you can’t even do it because of the way selectors and function pointers don’t really add up. But in Swift, we have a really powerful type system where we can express this.

I will show you the function prototype, which is a little bit overwhelming at first. This took a while to write, but it’s really not that complicated.

extension Task {
    func wrap<P1, P2, R1> (
        asyncFunction: (
            p1: P1,
            p2: P2,
            callback: (
                r1: R1,
                err: NSError?
            ) -> Void
        ) -> Void
    ) -> (P1, P2) -> Task<R1>
}

Unfortunately, Swift doesn’t have variadic generics, so this one is hard-coded for two parameters. What we have here is a function that takes an asyncFunction that takes two parameters, and a callback that takes a return value and an error. The callback and the asyncFunction both return nothing. The wrap function itself, however, returns a new function. This new function takes the same two parameters as asyncFunction, but instead of taking a callback, it returns a Task, parameterized with the same type as the parameter type of the callback. It’s kind of scary-looking, but in reality it’s rather straightforward.

extension Task {
    func wrap<P1, P2, R1> (
        asyncFunction: (
            p1: P1,
            p2: P2,
            callback: (
                r1: R1,
                err: NSError?
            ) -> Void
        ) -> Void
    ) -> (P1, P2) -> Task<R1>
    {
        let source = TaskCompletionSource<R1>()

        return { (p1: P1, p2: P2) -> Task<R1> in
            asyncFunc(p1: p1, p2: p2, callback: { (r1: R1, error: NSError?) -> Void in
                if let error = error {
                    source.failWithError(error)
                } else {
                    source.completeWithValue(r1)
                }
            })
            return source.task
        }
    }
}

In the implementation, we’re creating a task factory, and returning the new task-oriented function. It in turn just calls the original function, implements the callback, and signals the task to complete or fail, based on the values we get.

You’ve Rediscovered Monads (13:52)

When I gave a variant of this presentation for Objective-C a few years ago, someone came up to me afterwards and told me that I had rediscovered the monad. At the time, I hadn’t done enough functional programming to understand what he meant. I still haven’t quite, but I’m coming to realize that with functional programming, these things can be composed in really nice ways. I’m looking forward to learning more about that and doing another presentation about that once I’m “enlightened”.

If you’re interested in my thoughts on asynchrony, I’ve written an extremely ranty blog entry about it, so check that out. If you enjoy the idea of promises, but don’t think my implementation looks very good, there’s also PromiseKit and a few other libraries that I really encourage you to check out. Doing asynchrony without any kind of abstraction makes really bad code. It took me a long while to realize that, so now I’m really trying to figure out how to do it the right way. Right now, one of my favorite asynchronous features is await in C# which makes asynchronous code look synchronous, which I’ve also implemented for Objective-C. I’ll post about that soon, so keep an eye out!

If you have any questions or want to reach out, hit me on Twitter.


Nevyn Bengtsson

Nevyn Bengtsson

Nevyn is a co-founder of Lookback and was previously an iPhone developer for Spotify. From Sweden, Nevyn previously studied game programming at Blekinge Institute of Technology, and he likes making many things, including games, apps, robots, and music!