Created
March 5, 2015 08:34
-
-
Save DeFrenZ/32f8cd1fb4550261851c to your computer and use it in GitHub Desktop.
FuturePlayground
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
// Copyright (c) 2014 Rob Rix. All rights reserved. | |
// MARK: BoxType | |
/// The type conformed to by all boxes. | |
public protocol BoxType { | |
/// The type of the wrapped value. | |
typealias Value | |
/// Initializes an intance of the type with a value. | |
init(_ value: Value) | |
/// The wrapped value. | |
var value: Value { get } | |
} | |
/// The type conformed to by mutable boxes. | |
public protocol MutableBoxType: BoxType { | |
/// The (mutable) wrapped value. | |
var value: Value { get set } | |
} | |
// MARK: Equality | |
/// Equality of `BoxType`s of `Equatable` types. | |
/// | |
/// We cannot declare that e.g. `Box<T: Equatable>` conforms to `Equatable`, so this is a relatively ad hoc definition. | |
public func == <B: BoxType where B.Value: Equatable> (lhs: B, rhs: B) -> Bool { | |
return lhs.value == rhs.value | |
} | |
/// Inequality of `BoxType`s of `Equatable` types. | |
/// | |
/// We cannot declare that e.g. `Box<T: Equatable>` conforms to `Equatable`, so this is a relatively ad hoc definition. | |
public func != <B: BoxType where B.Value: Equatable> (lhs: B, rhs: B) -> Bool { | |
return lhs.value != rhs.value | |
} | |
// MARK: Map | |
/// Maps the value of a box into a new box. | |
public func map<B: BoxType, C: BoxType>(v: B, f: B.Value -> C.Value) -> C { | |
return C(f(v.value)) | |
} | |
// Copyright (c) 2014 Rob Rix. All rights reserved. | |
/// Wraps a type `T` in a reference type. | |
/// | |
/// Typically this is used to work around limitations of value types (for example, the lack of codegen for recursive value types and type-parameterized enums with >1 case). It is also useful for sharing a single (presumably large) value without copying it. | |
public final class Box<T>: BoxType, Printable { | |
/// Initializes a `Box` with the given value. | |
public init(_ value: T) { | |
self.value = value | |
} | |
/// The (immutable) value wrapped by the receiver. | |
public let value: T | |
/// Constructs a new Box by transforming `value` by `f`. | |
public func map<U>(f: T -> U) -> Box<U> { | |
return Box<U>(f(value)) | |
} | |
// MARK: Printable | |
public var description: String { | |
return toString(value) | |
} | |
} | |
/// Result | |
/// | |
/// Container for a successful value (T) or a failure with an NSError | |
/// | |
import Foundation | |
public protocol ErrorType {} | |
extension NSError: ErrorType {} | |
public let ErrorFileKey = "LMErrorFile" | |
public let ErrorLineKey = "LMErrorLine" | |
/// A success `Result` returning `value` | |
/// This form is preferred to `Result.Success(Box(value))` because it | |
// does not require dealing with `Box()` | |
public func success<T>(value: T) -> Result<T> { | |
return .Success(Box(value)) | |
} | |
/// A failure `Result` returning `error` | |
/// The default error is an empty one so that `failure()` is legal | |
/// To assign this to a variable, you must explicitly give a type. | |
/// Otherwise the compiler has no idea what `T` is. This form is preferred | |
/// to Result.Failure(error) because it provides a useful default. | |
/// For example: | |
/// let fail: Result<Int> = failure() | |
/// | |
private func defaultError(userInfo: [NSObject: AnyObject]) -> NSError { | |
return NSError(domain: "", code: 0, userInfo: userInfo) | |
} | |
private func defaultError(message: String, file: String = __FILE__, line: Int = __LINE__) -> NSError { | |
return defaultError([NSLocalizedDescriptionKey: message, ErrorFileKey: file, ErrorLineKey: line]) | |
} | |
private func defaultError(file: String = __FILE__, line: Int = __LINE__) -> NSError { | |
return defaultError([ErrorFileKey: file, ErrorLineKey: line]) | |
} | |
public func failure<T>(message: String, file: String = __FILE__, line: Int = __LINE__) -> Result<T> { | |
return failure(defaultError(message, file: file, line: line)) | |
} | |
public func failure<T>(file: String = __FILE__, line: Int = __LINE__) -> Result<T> { | |
return failure(defaultError(file: file, line: line)) | |
} | |
public func failure<T>(error: ErrorType) -> Result<T> { | |
return .Failure(error) | |
} | |
/// Construct a `Result` using a block which receives an error parameter. | |
/// Expected to return non-nil for success. | |
public func try<T>(f: NSErrorPointer -> T?, file: String = __FILE__, line: Int = __LINE__) -> Result<T> { | |
var error: NSError? | |
return f(&error).map(success) ?? failure(error ?? defaultError(file: file, line: line)) | |
} | |
public func try(f: NSErrorPointer -> Bool, file: String = __FILE__, line: Int = __LINE__) -> Result<()> { | |
var error: NSError? | |
return f(&error) ? success(()) : failure(error ?? defaultError(file: file, line: line)) | |
} | |
/// Container for a successful value (T) or a failure with an NSError | |
public enum Result<T> { | |
case Success(Box<T>) | |
case Failure(ErrorType) | |
/// The successful value as an Optional | |
public var value: T? { | |
switch self { | |
case .Success(let box): return box.value | |
case .Failure: return nil | |
} | |
} | |
/// The failing error as an Optional | |
public var error: ErrorType? { | |
switch self { | |
case .Success: return nil | |
case .Failure(let err): return err | |
} | |
} | |
public var isSuccess: Bool { | |
switch self { | |
case .Success: return true | |
case .Failure: return false | |
} | |
} | |
/// Return a new result after applying a transformation to a successful value. | |
/// Mapping a failure returns a new failure without evaluating the transform | |
public func map<U>(transform: T -> U) -> Result<U> { | |
switch self { | |
case Success(let box): | |
return .Success(Box(transform(box.value))) | |
case Failure(let err): | |
return .Failure(err) | |
} | |
} | |
/// Return a new result after applying a transformation (that itself | |
/// returns a result) to a successful value. | |
/// Flat mapping a failure returns a new failure without evaluating the transform | |
public func flatMap<U>(transform:T -> Result<U>) -> Result<U> { | |
switch self { | |
case Success(let value): return transform(value.value) | |
case Failure(let error): return .Failure(error) | |
} | |
} | |
} | |
extension Result: Printable { | |
public var description: String { | |
switch self { | |
case .Success(let box): | |
return "Success: \(box.value)" | |
case .Failure(let error): | |
return "Failure: \(error)" | |
} | |
} | |
} | |
/// Failure coalescing | |
/// .Success(Box(42)) ?? 0 ==> 42 | |
/// .Failure(NSError()) ?? 0 ==> 0 | |
public func ??<T>(result: Result<T>, defaultValue: @autoclosure () -> T) -> T { | |
switch result { | |
case .Success(let value): | |
return value.value | |
case .Failure(let error): | |
return defaultValue() | |
} | |
} |
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
// Playground - noun: a place where people can play | |
private let dispatchQueueLabel = "swift.future.task" | |
//private let completionTasksQueue = dispatch_queue_create("swift.future.completion", DISPATCH_QUEUE_CONCURRENT) | |
private let completionTasksQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) | |
enum FutureState<T> { | |
case Pending | |
case Completed(Result<T>) | |
} | |
private enum FutureTask<T> { | |
case OnComplete(Result<T> -> ()) | |
case OnSuccess(T -> ()) | |
case OnFailure(ErrorType -> ()) | |
} | |
class Future<T> { | |
private(set) var state: FutureState<T> { | |
didSet { | |
switch state { | |
case .Pending: | |
assertionFailure("A Future cannot be set as .Pending after initialisation.") | |
break | |
case .Completed(let result): | |
enqueuedTasks.map { | |
self.resolveTask($0, withResult: result) | |
} | |
} | |
} | |
} | |
private let queue: dispatch_queue_t! | |
private var enqueuedTasks: [FutureTask<T>] = [] | |
private init(result resultClosure: () -> Result<T>, executedInQueue queue: dispatch_queue_t?) { | |
self.state = .Pending | |
self.queue = queue ?? dispatch_queue_create(dispatchQueueLabel, nil) | |
dispatch_async(self.queue) { | |
self.state = .Completed(resultClosure()) | |
} | |
} | |
init(result: Result<T>) { | |
state = .Completed(result) | |
} | |
init(value: T) { | |
state = .Completed(success(value)) | |
} | |
init(error: ErrorType) { | |
state = .Completed(failure(error)) | |
} | |
func onSuccess(task: T -> ()) { | |
enqueuedTasks.append(.OnSuccess(task)) | |
} | |
func onFailure(task: ErrorType -> ()) { | |
enqueuedTasks.append(.OnFailure(task)) | |
} | |
func onComplete(task: Result<T> -> ()) { | |
enqueuedTasks.append(.OnComplete(task)) | |
} | |
var isCompleted: Bool { | |
switch state { | |
case .Completed: return true | |
default: return false | |
} | |
} | |
var result: Result<T>? { | |
switch state { | |
case .Completed(let result): return result | |
default: return nil | |
} | |
} | |
var value: T? { | |
switch state { | |
case .Completed(.Success(let value)): return value.value | |
default: return nil | |
} | |
} | |
var error: ErrorType? { | |
switch state { | |
case .Completed(.Failure(let error)): return error | |
default: return nil | |
} | |
} | |
private func resolveTask(task: FutureTask<T>, withResult result: Result<T>) { | |
switch (task, result) { | |
case (.OnComplete(let task), _): | |
dispatch_async(completionTasksQueue) { task(result) } | |
case (.OnSuccess(let task), .Success(let value)): | |
dispatch_async(completionTasksQueue) { task(value.value) } | |
case (.OnFailure(let task), .Failure(let error)): | |
dispatch_async(completionTasksQueue) { task(error) } | |
default: break | |
} | |
} | |
func ready() { | |
let semaphore = dispatch_semaphore_create(0) | |
onComplete { result -> () in | |
dispatch_semaphore_signal(semaphore) | |
return | |
} | |
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) | |
} | |
func await() -> Result<T> { | |
ready() | |
return result! | |
} | |
func map<U>(transform: T -> U) -> Future<U> { | |
return Future<U>(result: { () -> Result<U> in | |
switch self.state { | |
case .Pending: | |
assertionFailure("Executing a Future mapped on a .Pending Future") | |
return success(transform(self.value!)) | |
case .Completed(.Success(let value)): | |
return success(transform(value.value)) | |
case .Completed(.Failure(let error)): | |
return failure(error) | |
} | |
}, executedInQueue: queue) | |
} | |
func flatMap<U>(transform: T -> Future<U>) -> Future<U> { | |
return Future<U>(result: { () -> Result<U> in | |
transform(self.value!).await() | |
}, executedInQueue: queue) | |
} | |
} | |
func future<T>(task: () -> Result<T>) -> Future<T> { | |
return Future<T>(result: task, executedInQueue: nil) | |
} | |
func future<T>(inQueue queue: dispatch_queue_t, performingTask task: () -> Result<T>) -> Future<T> { | |
return Future<T>(result: task, executedInQueue: queue) | |
} | |
func future<T>(task: () -> T) -> Future<T> { | |
return Future<T>(result: { success(task()) }, executedInQueue: nil) | |
} | |
func future<T>(inQueue queue: dispatch_queue_t, performingTask task: () -> T) -> Future<T> { | |
return Future<T>(result: { success(task()) }, executedInQueue: queue) | |
} | |
func future<T>(task: @autoclosure () -> Result<T>) -> Future<T> { | |
return Future<T>(result: task, executedInQueue: nil) | |
} | |
func future<T>(inQueue queue: dispatch_queue_t, performingTask task: @autoclosure () -> Result<T>) -> Future<T> { | |
return Future<T>(result: task, executedInQueue: queue) | |
} | |
func future<T>(task: @autoclosure () -> T) -> Future<T> { | |
return Future<T>(result: { success(task()) }, executedInQueue: nil) | |
} | |
func future<T>(inQueue queue: dispatch_queue_t, performingTask task: @autoclosure () -> T) -> Future<T> { | |
return Future<T>(result: { success(task()) }, executedInQueue: queue) | |
} | |
import XCPlayground | |
let foo = future { Array(1 ... 100).map { $0 + 1 } } | |
foo.onSuccess { value in println("success: \(value)") } | |
foo.onSuccess { value in println("other success: \(value)") } | |
foo.onFailure { error in println("failure: \(error)") } | |
foo.onComplete { result in println("completed: \(result)") } | |
let bar = foo.map { $0.map { $0 + 2 } } | |
bar.onSuccess { value in println("map success: \(value)") } | |
let baz = bar.flatMap { value in future { value.map { $0 + 3 } } } | |
baz.onSuccess { value in println("flatMap success: \(value)") } | |
println("started future") | |
XCPSetExecutionShouldContinueIndefinitely() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment