getAsAsyncValue (analog of callAsFunction)

I'm not sure if this has been discussed already, (I'm doing my best to keep up to date on the concurrency discussions), but I've recently been prototyping at the boundary between the new concurrency APIs and Swift NIO. One thing I did early on was add an async get method in an extension of EventLoopFuture. This works well, but I ended up awash .get()s. This lead me to thinking it might be nice to have a facility in the language to describe "this value represents an async value", and the semantics for callAsFunction seem like a good fit. Specifically, you could implement the following:

struct MyExistingFuture<Value> {
  func getAsAsyncValue() async -> Value { … }
}
let future: MyExistingFuture = …
let value = await future
4 Likes

why not just append async to the regular callAsFunction?

2 Likes

why not just append async to the regular callAsFunction?
This could work, but would look weird in practice.

For instance, the NIO EventLoopFuture is often the result to a flatMap (or some other mapping operation). await foo.flatMap { … }() seems weird, as does await future(). At that point, I would prefer just using .get(). getAsAsyncValue() would allow you to leave off the calling parenthesis in both cases.

Perhaps API-level property wrapper on function arguments + effectful properties would have addressed this?

func work(@Future var body: String) {
  print(await body) // body.wrappedValue.get async -> String
}
1 Like

I wonder if this might be served well by effectful properties?

struct MyExistingFuture<Value> { 
  var value: Value { async get }
}

The one problem having an analog to callAsFunction is that it promotes heavier weight future types to abound. Comparatively speaking AsyncTask/Job are super lightweight. However on the other hand this would really be super nice to have on Task.Handle which has an async get function - and could perhaps be re-done to work like this.

1 Like

This seems analogous to Python’s notion of an awaitable object, which is anything that can be used in an await expression. I could see some value of adding this concept to the language to help bridge over pre-existing future types.

In its absence, we’re aware of the problem of proliferation of get. My advice here would be to avoid mixing future-style callback chains and async/await style linear code. This will usually mean wrapping NIO APIs as soon as possible in async functions so you can transition into async/await code. We’ve got some examples of this in the repository at the moment, where we have wrapped the current Future-oriented Channel API in async functions that hide the calls to get.

1 Like

Without doing something too radical, I'd say using the existing callAsFunction and allowing it to be async (if it's not done already) is a better short-term idea.

If we have something getAsAsyncValue then, for the sake of consistency the next question would be: "Why not also allow having a non-aync version of it?".
If the answer is "No", then how come specifically asynchronous version should be special?
If the answer is "Yes", then this means that Swift would get a full-fledged implicit conversion mechanism (which in and of itself is a very useful tool to have), which might not be what the core team would want Swift to have (I think I remember reading something about implicit conversions being a no-go for Swift in a manifesto somewhere, but I can't remember where).
If I'm mistaken about implicit conversions being a no-go (I really hope I am), then Swift's return value overloading would allow for overloading the same getAsValue with multiple return types.

5 Likes

This seems backwards to me. I much prefer the existing FP syntax and would rather see async/await wrapped in that, rather than the reverse. What's the NIO/SSWG view on this (I'm sort of assuming that we're far enough along for there to be one).

I believe we intend for higher-level user code to centralise on async/await. Lower-level NIO code will continue to eschew that model for the foreseeable future because we make deliberate guarantees about sharing an execution context that the async/await model is not entirely compatible with today.

1 Like