[Amendment] SE-0296: Allow overloads that differ only in async

If this is in a context without other trys undecorated with ? or !, wouldn't that make that context a "nonthrowing context?"

+1. This is great and I’m so glad we’re generalizing this rule outside of the obj-c imports! :heart:

—

One interaction I thought of was actors and our “implicitly async”, but it all ends up fine and understandable. :+1:

For context:

actor A { 
  func hi() {}
  func hi() async {} 
}

This means that effectively it is not possible to call the “synchronous” hi from outside the actor — because we always MUST call actor functions from outside as await hi() and thus we always call the async one. This is fine and good I think actually, so no concerns about this extension from my side :+1:

7 Likes

+1. I really like this change. The .NET developer in me looks forward to not having to suffix async function variants with Async, which will feel more at home when working with APIs that I only ever use asynchronously.

4 Likes

If I wanted to assign one of these two functions to a closure variable, how would I express which one of the two I want?

e.g.

let myClosure = doSomething  // How do I pick async vs non-async?
1 Like

I would assume you have to provide an explicit type.

let myClosure: () -> Void = doSomething
let myClosureAsync: () async -> Void = doSomething

But I'm not sure.

2 Likes

I wonder if SE-0315: Placeholder types could help, allowing you to write something like let myClosure: _ async = doSomething, though that doesn't seem to help specifying the "non async" case.

4 Likes

+1, this sounds very useful.

+1 this seems quite useful - I wish we would have come up with this sooner
 it might have allowed for different decisions to have been made

Yes, and it feels consistent with other effects.

Yes if you count swift’s throw effect

An in depth read. It does make me have the question “should we allow computed properties to be overloaded?”

Isn't it actually inconsistent with throws, the only other effect we've got? Or do you mean the obj-c import?

  • What is your evaluation of the proposal?

+1 (I wrote the diff)

  • Is the problem being addressed significant enough to warrant a change to Swift?

Yes. Such overloads already exist in the wild. For example, NSManagedObjectContext from Core Data provides both moc.perform { ... } and await moc.perform { ... }. The async version uses an extra argument with a default value, and this is how it avoids the redeclaration error. At the call site, though, everything works as if we had a pair that differ only in async.

  • Does this proposal fit well with the feel and direction of Swift?

It is consistent with ObjC imports that already profit from it, and it helps avoiding the .NET naming convention.

2 Likes

+1 to the proposal, of course. The lack of imagination about what people might want to overload has led to some boring threads about workarounds on this site that haven't amounted to problem-solving change. If you've got the resources to do so, please enable this. :smiley_cat::+1:

These are magnificent questions, and are on the way to the next two related proposals we need.

  1. Because many methods are going to be abandoned in favor of properties, now that get accessors have one new feature and one formerly-missing old one, we'll need the same overloading capabilities for those. (Keypath syntax does not handle this completely; get accessors basically can't be referred to directly. Functions will be easier to deal with but the users' shift will be to properties.)
  2. Generic computed variables have always been missing, but I expect more people will notice, more, with get accessors being more useful.

I think all of that could go into one grammar discussion thread.

Even with the placeholder types, it still would have to be written like this:

let myClosure: (_) -> _ = doSomething
let myClosureAsync: (_) async -> _ = doSomething
4 Likes

Will protocol requirements be allowed to differ only in asyncness?

E.g.

protocol P {
    func f()
    func f() async
}

Currently async requirements can be satisfied by synchronous functions. If the above is allowed, then can a synchronous function satisfy both overloads?

E.g.

struct S: P {
    func f() {} // enough to satisfy both 'f()' and 'f() async'?
}
4 Likes

This is very helpful information that, I hope, will be incorporated into the amended proposal text. It bears calling out explicitly that, in an async context, writing a bare function call to the sync function will not work, but that there are ways to invoke it.

For greater clarity, is @Jon_Shier correct that writing let f2: () -> Void = doSomething; f2() will also work in the same way in an async context?

I will be happy to help again if the core team feels like it is necessary to do so, especially if it can avoid an "Accepted with modifications" conclusion :-)

Yes.

And yes.

Doug

4 Likes

I agree!

We'd definitely appreciate that! I consider this a clarification of the existing semantics, so you can update the PR with this if you'd like.

Doug

3 Likes

Do you know if the suggested fix-its will include both options by default or if there is any way to know that you've only implemented one of the two function requirements of the protocol? Or is there any way to mark that you'd like the sync version to also apply for the async. I often rely pretty heavily on the compiler to help me implement protocols, so I would hope that it would fill in both as options, letting you delete one, or warning you in some way that you might be shadowing an async function requirement.

1 Like

Done in this commit. cc @xwu

(EDIT: fixed commit link after a force-push)

1 Like

Great. And would you mind including the example where a closure can be used to select the synchronous overload in an asynchronous context (and, I think, binding the function to an explicitly typed variable—unless I misunderstand)?