Skip to content

Instantly share code, notes, and snippets.

@DivineDominion
Created March 1, 2015 19:27
Show Gist options
  • Save DivineDominion/21446ef37ac87c62567b to your computer and use it in GitHub Desktop.
Save DivineDominion/21446ef37ac87c62567b to your computer and use it in GitHub Desktop.
Paste this into a playground and see what happens with the two client code examples at the bottom. Should work out of the box.
import Cocoa
/// A wrapper around any type to make Result compile
class Box<T> {
let unbox: T
init(_ value: T) {
self.unbox = value
}
}
/// Generic return type for either a happy-path result, or
/// an NSError
enum Result<T> {
case Value(Box<T>)
case Error(NSError)
}
extension Result {
func map<U>(f: T -> U) -> Result<U> {
switch self {
case let .Value(value):
return Result<U>.Value(Box(f(value.unbox)))
case let .Error(error):
return Result<U>.Error(error)
}
}
static func flatten<T>(result: Result<Result<T>>) -> Result<T> {
switch result {
case let .Value(innerResult):
return innerResult.unbox
case let .Error(error):
return Result<T>.Error(error)
}
}
func transform<U>(f: T -> Result<U>) -> Result<U> {
return Result.flatten(map(f))
}
}
/// Regular approach: fetch data from URL and populate `error` accordingly. Doesn't keep return value and `error` in sync.
func fetchData(url : NSURL, error errorPointer : NSErrorPointer) -> NSData? {
var error : NSError?
let data = NSData(contentsOfURL:url, options: NSDataReadingOptions(rawValue:0), error: &error)
if (data == nil) {
if errorPointer != nil {
errorPointer.memory = error
}
return nil
}
return data
}
/// Proposed approach. Instead of nil+NSError, returns a NSError-based result only. No nil checking necessary.
func fetchDataResult(url : NSURL) -> Result<NSData> {
var error : NSError?
if let data = fetchData(url, error: &error) {
return Result.Value(Box(data))
}
return Result.Error(error!)
}
func BuildError(code: Int, description: String) -> NSError {
return NSError(domain: "de.christiantietze.blogpost", code: code, userInfo: [NSLocalizedDescriptionKey: description])
}
func stringFromData(data : NSData, error errorPointer : NSErrorPointer) -> NSString? {
let string = NSString(data: data, encoding: NSUTF8StringEncoding)
if (string == nil) {
if errorPointer != nil {
errorPointer.memory = BuildError(1, "Unable to build string from data")
}
return nil
}
return string
}
func stringFromDataResult(data : NSData) -> Result<NSString> {
var error : NSError?
if let string = stringFromData(data, error: &error) {
return Result.Value(Box(string))
}
return Result.Error(error!)
}
/// Conventional conversion from URL to data to string.
func StringFromURL(url : NSURL, error errorPointer : NSErrorPointer) -> (NSString?) {
let data = fetchData(url, error: errorPointer)
if (data == nil) {
return nil
}
let string = stringFromData(data!, error: errorPointer)
if (string == nil) {
return nil
}
return string
}
func stringFromNSString(aString : NSString) -> Result<String> {
return Result.Value(Box(String(aString)))
}
/// Proposed conversion using transformations. Added NSString-to-String conversion just for the sake of it.
func StringFromURLResult(url : NSURL) -> Result<String> {
return fetchDataResult(url).transform { stringFromDataResult($0) }.transform { stringFromNSString($0) }
}
//
// Client code using monads
//
let zenAPI = NSURL(string: "https://api.github.com/zen")
let stringResult: Result<String> = StringFromURLResult(zenAPI!)
switch (stringResult) {
case .Value(let value):
value.unbox
// work with value
case .Error(let error):
error
// work with error
}
//
// Conventional client code
//
var error : NSError?
if let conventionalStringResult = StringFromURL(zenAPI!, error: &error) {
conventionalStringResult
// work with conventionalStringResult
} else {
error
// work with error
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment