How to Train your Monad

I’ve already shown you how to handle errors and why transforms are great for application architecture. Let’s put all that stuff together to finally achieve our goal: Chaining transforms with error handling.

A short recap of our goal

We wanted to achieve a way of programming that is similar to the unix pipe:

ls | grep .jpg | sort

Methods in this way of programming should have the following characteristics:

  1. We don’t tell them how to do it, we tell it what we want instead.
  2. Every command has a well defined interface (stdin, stdout, errout).
  3. Commands are chainable.
  4. Error handling is implicit.

Also we defined 4 method signatures for transforms:

// synchronous, non failing
func transform<A,B>(value: A)->B

// async, non failing
func transform<A,B>(value: A, completion: (B->Void))

// synchronous, failable
func transform<A,B>(value: A)->Result<B>
func transform<A,B>(value: A) throws -> B //Swift 2 version

// async, failable
func transform<A,B>(value: A, completion: (Result<B>->Void))

Adding the missing map

We’ve already implemented the synchronous map function on the Result<T>. Let’s add the asynchronous one:

public func map<U>(f:(T, (U->Void))->Void) -> (Result<U>->Void)->Void {
  return { g in
    switch self {
    case let .Success(v): f(v.value){ transformed in
        g(.Success(transformed))
      }
    case let .Error(error): g(.Error(error))
    }
  }
}

This method takes an asynchronous non failing transform (the 2nd one in our list) and returns a function that can be invoked with a completion block to get the result once it’s completed like in this example:

func toHash(string: String, completion: Int->Void) {
  completion(count(string))
}

Result.Success("Hello World").map(toHash)(){ result in
  //result is now a .Success of Int with the value 11
}

This might feel a bit clunky at first, but the advantages are pretty obvious as soon as we start to chain synchronous transforms that can fail.

Failable Transforms

Let’s consider applying a failable transform to a Result<T>:

func toInt(string: String)->Result<Int>{
  if let int = string.toInt() {
    return .Success(int)
  } else {
    return .Error(NSError())
  }
}

Result.Success("Hello World").map(toInt)(){ result in
  // result is now of type Result<Result<Int>>
}

Mapping to a result of a result is actually pretty useless. In the end we’d prefer to either have success or a failure. Most languages call this feature flatMap (because it first maps and then flattens the result), fmap or bind. I’ll go with flatMap here (there’s something similar on Optional and Array in Swift2).

public func flatMap<U>(f: T -> Result<U>) -> Result<U> {
  switch self {
  case let .Success(v): return f(v)
  case let .Error(error): return .Error(error)
  }
}

If the result is a success, the next function is executed, if it’s a failure the error is returned immediateley. The previous example now looks like this:

Result.Success("Hello World").flatMap(toInt)(){ result in
  // result is finally of type Result<Int>
}

This looks a lot nicer. There is still one transform missing: The async failable transform:

public func flatMap<U>(f:(T, (Result<U>->Void))->Void) -> (Result<U>->Void)->Void {
  return { g in
    switch self {
    case let .Success(v): f(v, g)
    case let .Error(error): g(.Error(error))
    }
  }
}

This method returns a completion handler that can be used to grab the result once it’s completed:

func toInt(string: String, completion:Result<Int>->Void){
  if let int = string.toInt() {
    completion(.Success(int))
  } else {
    completion(.Error(NSError()))
  }
}

Result.Success("Hello World").flatMap(toInt)(){ result in
  // result is again of type Result<Int>
}

Method Chains

Now we can finally do our unix example:

ls | grep .jpg | sort
let ls = Result.Success(["/home/me.jpg", "home/data.json"])
let grep: String->([String]->Result<[String]>) = { pattern in
    return { paths in
        return .Success(paths)
    }
}
let sort: [String]->Result<[String]> = { values in
    return .Success(values.sort())
}

let chain = ls.flatMap(grep(".jpg")).flatMap(sort) //Result<Array>

Let’s revisit our wishlist:

1. We don’t tell them how to do it, we tell it what we want instead.

We’ve written small, composable functions that can be chained together. They’re reusable and easily testable.

2. Every command has a well defined interface

All tansforms take a value, manipulate a copy and either returns a .Success or an .Error.

3. Commands are chainable.

By using map and flatMap we can chain functions and get a result (this is not yet working for async transforms - we’ll do something about that in the next post). By using generics we can be sure that only matching functions can be concatenated (this is actually an advancement over the unix implementation).

4. Error handling is implicit.

If an error happens during a transform, all further transforms are skipped and an .Error is returned instead. There is no chance to forget an error handling branch (and no “I’ll do this later //Fixme:”).

The monad and you

What we’ve just implemented is also called a monad. A monad is a thing that as a constructor and that defines flatMap. If you want to read more about monads, burritos and boxes take a look at fuckingmonads.com. Also there is a great explanation at Functors, Applicatives, And Monads In Pictures.

A deeper look into space

We’ve seen a great application for synchronous flatMap methods - but handling async stuff is still missing. In the next chapter we’ll take a look at Signal<T> and how to tame callback hell. In the end you will have seen the full implementation of Interstellar, the reactive programming framework. We’re almost there!

Read more posts from this series:
  1. A Swifter Way Of Handling Errors
  2. Transforming The World Into A Better Place
  3. How To Train Your Monad
  4. Sending A Signal To Space
  5. The Signal, Threading and You.
  6. Wrapping View Controllers in Signals