Futures/Promises proof of concept in Swift for Linux (first draft)
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
/* Basic (Scala-esque) Futures/Promises in Swift on Linux (first draft) | |
* Author: Andrew Bulhak (https://github.com/andrewcb/) | |
* Distributed under Creative Commons CC-BY-SA. Some rights reserved. | |
* | |
* Note: this code will not be performant until there's a proper | |
* libdispatch available. | |
*/ | |
import Foundation | |
import NSLinux | |
import Glibc | |
/* POSIX semaphores used for awaiting Futures */ | |
let NSEC_PER_SEC = 1000000000 | |
extension timespec { | |
mutating func addnsec(nsec: Int) { | |
let nnsec = tv_nsec + nsec | |
tv_nsec = nnsec % NSEC_PER_SEC | |
tv_sec += nnsec / NSEC_PER_SEC | |
} | |
} | |
extension sem_t { | |
mutating func getvalue() -> Int32 { | |
var v: Int32 = 0 | |
sem_getvalue(&self, &v) | |
return v | |
} | |
mutating func timedwait(inout deadline: timespec) -> Int32 { | |
return sem_timedwait(&self, &deadline) | |
} | |
mutating func post() { | |
sem_post(&self) | |
} | |
} | |
/** | |
A Future is a container for a computation of some sort whose result may not yet be | |
available. It can be instantiated with a value or a block of code, to execute asynchronously, | |
producing this value. | |
*/ | |
class Future<T> { | |
typealias Element = T | |
typealias SuccessCallback = T -> () | |
var state: T? | |
var successCallbacks: [SuccessCallback] = [] | |
var valueSemaphore: sem_t = sem_t() | |
/** initialise an unfulfilled Future */ | |
init() { | |
sem_init(&valueSemaphore, 0, 0) | |
} | |
/** initialise a Future with a block of code to run asynchronously, | |
computing a value. | |
*/ | |
init(future: ()->T) { | |
sem_init(&valueSemaphore, 0, 0) | |
dispatch_async ( dispatch_get_global_queue(0, 0), { | |
self._complete(future()) | |
}) | |
} | |
/** initialise a Future with an immediately available value; slightly | |
more efficient than firing off a block. */ | |
init(immediate: T) { | |
sem_init(&valueSemaphore, 0, 0) | |
self._complete(immediate) | |
} | |
deinit { | |
sem_destroy(&valueSemaphore) | |
} | |
private func _complete(value: T) { | |
self.state = value | |
self.valueSemaphore.post() | |
for cb in self.successCallbacks { | |
cb(value) | |
} | |
} | |
/** Adds a callback to be called on successful completion. */ | |
func onSuccess(action: SuccessCallback) { | |
successCallbacks.append(action) | |
if let value = self.state { | |
action(value) | |
} | |
} | |
/** map: creates a Future of type U from a Promise of type T and a T->U */ | |
func map<U>(transform: T->U) -> Future<U> { | |
let r = Promise<U>() | |
self.onSuccess { | |
r.complete(transform($0)) | |
} | |
return r.future() | |
} | |
/** flatMap: allows the chaining of futures */ | |
func flatMap<U>(transform: T->Future<U>) -> Future<U> { | |
let r = Promise<U>() | |
self.onSuccess { (v1: T) -> () in | |
let p2 = transform(v1) | |
p2.onSuccess { | |
r.complete($0) | |
} | |
} | |
return r.future() | |
} | |
/** wait for a maximum amount of time for the Future to be fulfilled. */ | |
func await(time: NSTimeInterval) -> T? { | |
let nsec = Int(time * 1000000000) | |
var ts: timespec = timespec() | |
clock_gettime(CLOCK_REALTIME, &ts) | |
ts.addnsec(nsec) | |
valueSemaphore.timedwait(&ts) | |
valueSemaphore.post() | |
return self.state | |
} | |
} | |
/** | |
A Promise is a container for a possibly not yet available value like a Future, only | |
with the facility for whatever process holds it to complete it by supplying this value. | |
It can also return a linked copy of itself as a (read-only) Future. | |
*/ | |
class Promise<T> : Future<T> { | |
override init() { super.init() } | |
/** called by whatever process computes the Promise's value to complete it. | |
*/ | |
func complete(value: T) { | |
self._complete(value) | |
} | |
func future() -> Future<T> { | |
return self as Future<T> | |
} | |
} | |
// | |
// ----------------------------------------------------------------- | |
// | |
/** two chained promises, fulfilled by an independent process completing them */ | |
var p1 = Promise<Int>() | |
var f2 = p1.map { "-\($0)-" } | |
dispatch_async ( | |
dispatch_get_global_queue(0, 0), { | |
sleep(1) | |
p1.complete(3) | |
}) | |
/** a Promise defined using the Future syntax */ | |
var f3 = Future<Int>( future: { sleep(2); return 23 }).flatMap { (v:Int)->(Future<Int>) in Future<Int>( future: { sleep(1); return v*2; })} | |
print("waiting...") | |
let v2 = f2.await(3.0) | |
print("v2 = \(v2)") | |
let v3 = f3.await(3.0) | |
print("v3 = \(v3)") | |
/** This promise will take too long for the await */ | |
let fGodot = Future<String>( future: { sleep(10); return "Hello"}) | |
let vGodot = fGodot.await(1.0) | |
print("Gave up waiting: result = \(vGodot)") | |
/** An immediate Promise; this should not wait */ | |
let fImmediate = Future<String>(immediate: "Here I am!") | |
print("A very short wait...") | |
let vImmediate = fImmediate.await(1.0) | |
print("Immediate result = \(vImmediate)") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment