Created
March 1, 2015 19:27
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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