Sorry, I had got some confusion. Please let me retry to explain. As you said, C# provides three kinds of async functions: `async Void`, `async Task` and `async Task<Foo>`. All of them are necessary and Swift should provide same functionalities.
When we think about `async/await` in Swift, because we have already had `throws/try`, it is desired that `async/await` in Swift is consistent with `throws/try`. So it is better to have `async/await` without introducing a type like `Task` (or `Promise`). Even if we employ `async/await` without `Task`, Swift has to provides functionalities to implement "three kinds of async functions" in C#. However if `async -> Void` in Swift works similarly to `async Void` in C#, how can we express ones like `async Task` in C#? I think there are two possibilities: 1. Calling `async -> Void` functions without `await` in Swift works like `async Void` in C# and calling them *with* `await` works like `async Task` in C#. 2. Calling `async -> Void` functions without `await` in Swift works like `async Void` in C# and never support something like `async Task` in C#. I think 2 is impermissible. For example, handling completion events of asynchronous operations without result values needs something like `async Task` in C#. However, with 1, we lose the benefit of static checks by the compiler. Because both of `fooAsync()` without `await` and `await fooAsync()` are allowed, even if we want it to work like `async Task` in C# and forget to mark `await`, the compiler tell us nothing and it works like `async Void` in C#. It causes unexpected behaviors. It is hard to fix such kinds of bugs. So I think introducing `beginAsync` is better. -- Yuta 2017-11-12 10:23 GMT+09:00 Yuta Koshizawa via swift-evolution <[email protected]>: > 2017-11-12 2:57 GMT+09:00 Adam Kemp <[email protected]>: >> >> >>> On Nov 11, 2017, at 6:24 AM, Yuta Koshizawa <[email protected]> wrote: >>> >>> If you replace `async` with `throws`, you can get answers. >>> >>> >>>> Can you declare an async closure variable? >>> >>> Yes. Like `let throwingClosure:() throws -> Void = { ... }`. >>> >>> >>>> Can a non-async closure be passed to a function expecting a async closure? >>> >>> Yes. Like we can pass `() -> Void` to a function expecting a throwing >>> closure `() throws -> Void`. >>> >>> It is possible because `(Foo) throws -> Bar` is a supertype of `(Foo) >>> -> Bar`. `(Foo) async -> Bar` is a supertype of `(Foo) -> Bar` in the >>> same way. >>> >>> To treat an async function as a sync function is legal. It is similar >>> to make a `Promise` by `Promise(value)` which is completed >>> immediately. >>> >>> >>>> Can an async closure be passed to a function expecting a non-async closure? >>> >>> No. `() -> Void` is a subtype of `() async -> Void`. It is same as >>> passing `() throws -> Void` to a function expecting `() -> Void` is >>> not allowed. >> >> But why not? Just asserting that it must work the same as throws >> is not a convincing argument. You have to justify why it must work >> that way. I think there is good reason to allow it, which I have described. >> What reason is there to disallow it? > > `() async -> Void` needs to be called with `await` because it prevents > us from forgetting handling asynchronous operations. > > If we use callbacks to handle asynchronous operations, it is shown to > us by a compiler as a compilation error. > > ``` > func fooAsync(_ handler: () -> Void) -> Void { ... } > > fooAsync() // compilation error > > fooAsync { > // handles a completion event here > } > ``` > > With proposed `async/await`, it is realized similarly like below. > > ``` > func fooAsync() async -> Void { ... } > > fooAsync() // compilation error > > await fooAsync() > // handles a completion event here > ``` > > However, if async void functions work like `beginAsync`, we can easily > forget it and it can cause unexpected behaviors. > > ``` > func fooAsync() async -> Void { ... } > > fooAsync() // OK > // hard to know this line is executed asynchronously > ``` > > Readability also suffers seriously. If we don't know `bar` in the > following code is a async function, it is impossible to expect lines > after `baz()` are executed asynchronously. > > ``` > foo() > bar() > baz() > qux() > ``` > > >>>> It’s weird to me that we would allow you to have async void closures but >>>> not async void functions >>> >>> I am not sure what you mean. "async void closures" and "async void >>> functions" have a same type. Following two are almost same. >>> >>> ``` >>> func foo() async -> Void { ... } >>> let foo: () async -> Void = { ... } >>> ``` >> >> What started this thread is my suggestion that you should be able to write >> an async void function. The current proposal doesn’t allow that. That’s why >> you have to use beginAsync. >> >> I don’t think that makes sense. It sounds like you also think that would be >> strange, >> hence your assumption that you could. > > By the reasons I wrote above, we need `await` even for async void > functions for checks by compilers. Then it is required to provide a > way to write entry points of async functions. That is `beginAsync`. > > -- > Yuta > _______________________________________________ > swift-evolution mailing list > [email protected] > https://lists.swift.org/mailman/listinfo/swift-evolution _______________________________________________ swift-evolution mailing list [email protected] https://lists.swift.org/mailman/listinfo/swift-evolution
