here's my attempt of quick and dirty callback implementation of async/await.
with this approach the original fragment would be written almost as in the proposal:
func processImageData1() -> Async<UIImage> {
let dataResource = loadWebResource("dataprofile.txt")
let imageResource = loadWebResource("imagedata.dat")
let imageTmp = decodeImage(dataResource, imageResource)
let imageResult = dewarpAndCleanupImage(imageTmp)
return imageResult
}
where the individual methods are typed as:
func loadWebResource(_ path: String) -> Async<Data>
func decodeImage(_ a: Async<Data>, _ b: Async<Data>) -> Async<UIImage>
func dewarpAndCleanupImage(_ a: Async<UIImage>) -> Async<UIImage>
in this approach Async is simulated as a callback that returns a thing and await is simulated as the callback call to get the thing. note how the concept of autoclosure intertwines with async here. showing example with some simple math to illustrate the gust of the concept.
typealias Async<T> = () -> T
func await<T>(_ x: Async<T>) -> T {
x()
}
func const<T>(_ x: T) -> Async<T> { // see autoclosure example below
{ x }
}
func sin(_ x: @escaping Async<Double>) -> Async<Double> {
{ sin(await(x)) } // system sin is a sync function -> await
}
func cos(_ x: @escaping Async<Double>) -> Async<Double> {
{ cos(await(x)) }
}
func + (_ x: @escaping Async<Double>, _ y: @escaping Async<Double>) -> Async<Double> {
{ await(x) + await(y) } // built-in + is a sync function -> await
}
func - (_ x: @escaping Async<Double>, _ y: @escaping Async<Double>) -> Async<Double> {
{ await(x) - await(y) }
}
func ^ (_ x: @escaping Async<Double>, _ y: Double) -> Async<Double> {
{ pow(await(x), y) }
}
func ^ (_ x: @escaping Async<Double>, _ y: @escaping Async<Double>) -> Async<Double> {
x ^ await(y) // await second argument and forward to another async function
}
func sqrt(_ x: @escaping Async<Double>) -> Async<Double> {
{ sqrt(await(x)) }
}
func someComplexThing(_ x: @escaping Async<Double>, _ y: @escaping Async<Double>) -> Async<Double> {
sqrt(sin(x) ^ 2 + cos(x) ^ 2) // all what's called here is async function -> no await
}
func bar(_ x: @escaping @autoclosure Async<Double>, _ y: @escaping @autoclosure Async<Double>) -> Double {
await(someComplexThing(x, y)) // bar is sync function -> await
}
func baz(_ x: @escaping @autoclosure Async<Double>, _ y: @escaping @autoclosure Async<Double>) -> Async<Double> {
someComplexThing(x, y) // baz is async function -> no await
}
func poo(_ x: @escaping Async<Double>, _ y: @escaping Async<Double>) -> Async<Double> {
someComplexThing(x, y) // poo is async function -> no await
}
func testFooBar() {
let r = bar(1, 2)
print(r)
let z = await(baz(1, 2))
print(z)
let w = await(poo(const(1), const(2)))
print(w)
}
note that this is more akin to a "future/promise" approach (returning a callback that can be used to get a thing instead of returning the thing itself) compared to that transformation with the chained callback calls where function result is moved to the last parameter as a closure. both approaches are valid, just one is easier to implement without language/compiler changes.
ps. i wish (and looking at the above fragment it is obvious why) it was possible to embed "@escaping" specification inside the typealias definition itself.