Skip to content

Instantly share code, notes, and snippets.

@landonf
Last active June 2, 2016 06:49
Show Gist options
  • Save landonf/539354d19175c9e5239b to your computer and use it in GitHub Desktop.
Save landonf/539354d19175c9e5239b to your computer and use it in GitHub Desktop.
Swift Result Type

A result type (based on swiftz) that can represent either an error or success:

enum Result<X, T> {
  case Err(() -> X)
  case Ok(() -> T)
}

Now we need a way to chain multiple results together without lots of nesting of if statements -- or exceptions. To do so, we can define a new bind (result, next) operator (implementation borrowed from swiftz) that operates on Result types (a.k.a flatMap or >>=):

  • If the result is Err, the result is immediately returned.
  • If the result is Ok, the next function is called with the current result value. The next function must also return an instance of Result, and thus allow chaining of binds.

bind:

// The name `bind' operator is named `>>=', borrowed from Haskell.
func >>=<X, T, NT>(result: Result<X, T>, next: T -> Result<X, NT>) -> Result<X, NT> {
  switch result {
    case let .Err(l): return .Err(l)
    case let .Ok(r): return next(r())
  }
}

It'd also be handy to be able to map over the successful value in a Result without having to return a new result; we can use swiftz's <^> for this:

  • If the result is Err, the result is immediately returned.
  • If the result is Ok, the next function is called with the current result value. The next function may return any type, mapping the current result value to a new value.

map:

operator infix <^> {
  associativity left
}

func <^><X, T, NT>(result: Result<X, T>, next: T -> NT) -> Result<X, NT> {
  switch result {
    case let .Err(l): return .Err(l)
    case let .Ok(r): return .Ok({next(r())})
  }
}

Now, we can demonstrate how chaining works by putting it all together into a hopefully comprehensible simple network client API:

/* Putting it all together */
func main () {
  let client = Connection.openConnection("example.org", 25, DnsResolver()) <^> { SMTPClient($0) }
  switch client {
    case .Err(let networkError): println("Failed to connect with network error: \(networkError().message)")
    case .Ok(let client): println("Got connection with client \(client())")
  }
}

/* Our own error class for network errors. This would be similar to NSError's error domain,
 * but with types. We could also define an enum ADT instead, and have exhaustive error match
 * validation in switch statements */
class NetworkError {
  let message:String
  init (_ message: String) { self.message = message }
}

/* Our own not-very-useful InetAddress class. If this was a useful API, we'd support converting
 * an InetAddress to a connectable socket */
class InetAddress {
  func socket () -> Result<NetworkError, Int> { return .Err({NetworkError("Sorry, I'm the world's most useless InetAddress class")}) }
}

/* DNS Resolution */
class DnsResolver {
  func lookup (host: String) -> Result<NetworkError, InetAddress> {
    return .Err({NetworkError("Sorry, I'm the world's most useless DNS resolver")})
  }
}

/* Our TCP connection class */
class Connection {
  /* Open a connection to the given host and port */
  class func openConnection (host: String, _ port: Int, _ resolver: DnsResolver) -> Result<NetworkError, Connection> {
    // This could be written with a nice one-liner, but we'll break it out for clarity:
    // return (resolver.lookup(host) >>= { $0.socket() }) <^> { Connection($0) }

    /* Perform the DNS lookup and return a socket */
    let sockfd: Result<NetworkError, Int> = resolver.lookup(host) >>= { $0.socket() }
    
    /* Return the new connection instance */
    return sockfd <^> { Connection($0) };
  }
  
  // private initializer
  init (_ socket: Int) {}
}

/* Our SMTP client class */
class SMTPClient {
  init (_ conn: Connection) {}
}

If you run main(), you should see that the chaining short-circuited at the first error, in our only-returns-errors DnsResolver:

landonf@lambda> swift result-chaining.swift && ./result-chaining 
Failed to connect with network error: Sorry, I'm the world's most useless DNS resolver
/* Putting it all together */
func main () {
let client = Connection.openConnection("example.org", 25, DnsResolver()) <^> { SMTPClient($0) }
switch client {
case .Err(let networkError): println("Failed to connect with network error: \(networkError().message)")
case .Ok(let client): println("Got connection with client \(client())")
}
}
enum Result<X, T> {
case Err(() -> X)
case Ok(() -> T)
}
// The name `bind' operator is named `>>=', which was borrowed from Haskell.
func >>=<X, T, NT>(result: Result<X, T>, next: T -> Result<X, NT>) -> Result<X, NT> {
switch result {
case let .Err(l): return .Err(l)
case let .Ok(r): return next(r())
}
}
operator infix <^> {
associativity left
}
func <^><X, T, NT>(result: Result<X, T>, next: T -> NT) -> Result<X, NT> {
switch result {
case let .Err(l): return .Err(l)
case let .Ok(r): return .Ok({next(r())})
}
}
/* Our own error class for network errors. This would be similar to NSError's error domain,
* but with types. We could also define an enum ADT instead, and have exhaustive error match
* validation in switch statements */
class NetworkError {
let message:String
init (_ message: String) { self.message = message }
}
/* Our own not-very-useful InetAddress class. If this was a useful API, we'd support converting
* an InetAddress to a connectable socket */
class InetAddress {
func socket () -> Result<NetworkError, Int> { return .Err({NetworkError("Sorry, I'm the world's most useless InetAddress class")}) }
}
/* DNS Resolution */
class DnsResolver {
func lookup (host: String) -> Result<NetworkError, InetAddress> {
return .Err({NetworkError("Sorry, I'm the world's most useless DNS resolver")})
}
}
/* Our TCP connection class */
class Connection {
/* Open a connection to the given host and port */
class func openConnection (host: String, _ port: Int, _ resolver: DnsResolver) -> Result<NetworkError, Connection> {
// This could be written with a nice one-liner, but we'll break it out for clarity:
// return (resolver.lookup(host) >>= { $0.socket() }) <^> { Connection($0) }
/* Perform the DNS lookup and return a socket */
let sockfd: Result<NetworkError, Int> = resolver.lookup(host) >>= { $0.socket() }
/* Return the new connection instance */
return sockfd <^> { Connection($0) };
}
// private initializer
init (_ socket: Int) {}
}
/* Our SMTP client class */
class SMTPClient {
init (_ conn: Connection) {}
}
main()
@samskivert
Copy link

Why bind sockfd? You're just doing a map there: sockfd <^> { Connection($0) }

@samskivert
Copy link

And of course, I'd advocate doing all that in one line above. I loves me a good one liner.

@landonf
Copy link
Author

landonf commented Jun 8, 2014

Oh, whoops. I wrote the sockfd line before I integrated <^>. The non-one-liner was just to be able to show the inferred types more clearly.

@landonf
Copy link
Author

landonf commented Jun 8, 2014

Updated with map and one-linery goodness.

@protocool
Copy link

Unless I'm missing a benefit of actually using closures for the Result enum, I think things would be less noisy as this:

enum Result<X, T> {
  case Err(@auto_closure () -> X)
  case Ok(@auto_closure () -> T)
}

Then you can use it like so:

return .Err(NetworkError("Sorry, I'm the world's most useless InetAddress class"))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment