I’d say it’s a little premature to be talking about this; the team has made it 
very clear that the discussion on Native Concurrency in Swift won’t begin for 
another couple months.

Dan

> On Jul 14, 2016, at 10:54 AM, Charles Srstka via swift-evolution 
> <[email protected]> wrote:
> 
> I know it’s late, but I was wondering what the community thought of this:
> 
> MOTIVATION:
> 
> With the acceptance of SE-0112, the error handling picture looks much 
> stronger for Swift 3, but there is still one area of awkwardness remaining, 
> in the area of returns from asynchronous methods. Specifically, many 
> asynchronous APIs in the Cocoa framework are declared like this:
> 
> - (void)doSomethingWithFoo: (Foo *)foo completionHandler: (void (^)(Bar * 
> _Nullable, NSError * _Nullable))completionHandler;
> 
> This will get imported into Swift as something like this:
> 
> func doSomething(foo: Foo, completionHandler: (Bar?, Error?) -> ())
> 
> The intention of this API is that either the operation will succeed, and 
> something will be passed in the Bar parameter, and the error will be nil, or 
> else the operation will fail, and then the error parameter will be populated 
> while the Bar parameter is nil. However, this intention is not expressed in 
> the API, since the syntax leaves the possibility that both parameters could 
> be nil, or that they could both be non-nil. This forces the developer to do 
> needless and repetitive checks against a case which in practice shouldn’t 
> occur, as below:
> 
> doSomething(foo: foo) { bar, error in
>       if let bar = bar {
>               // handle success case
>       } else if let error = error {
>               self.handleError(error)
>       } else {
>               self.handleError(NSCocoaError.FileReadUnknownError)
>       }
> }
> 
> This results in the dreaded “untested code.”
> 
> Note that while it is possible that the developer could simply force-unwrap 
> error in the failure case, this leaves the programs open to crashes in the 
> case where a misbehaved API forgets to populate the error on failure, whereas 
> some kind of default error would be more appropriate. The do/try/catch 
> mechanism works around this by returning a generic _NilError in cases where 
> this occurs.
> 
> PROPOSED SOLUTION:
> 
> Since the pattern for an async API that returns an error in the Cocoa APIs is 
> very similar to the pattern for a synchronous one, we can handle it in a very 
> similar way. To do this, we introduce a new Result enum type. We then bridge 
> asynchronous Cocoa APIs to return this Result type instead of optional 
> values. This more clearly expresses to the user the intent of the API.
> 
> In addition to clarifying many Cocoa interfaces, this will provide a standard 
> format for asynchronous APIs that return errors, opening the way for these 
> APIs to be seamlessly integrated into future asynchronous features added to 
> Swift 4 and beyond, in a way that could seamlessly interact with the 
> do/try/catch feature as well.
> 
> DETAILED DESIGN:
> 
> 1. We introduce a Result type, which looks like this:
> 
> enum Result<T> {
>       case success(T)
>       case error(Error)
> }
> 
> 2. Methods that return one parameter asynchronously with an error are bridged 
> like this:
> 
> func doSomething(foo: Foo, completionHandler: (Result<Bar>) -> ())
> 
> and are used like this:
> 
> doSomething(foo: foo) { result in
>       switch result {
>       case let .success(bar):
>               // handle success
>       case let .error(error):
>               self.handleError(error)
>       }
> }
> 
> 3. Methods that return multiple parameters asynchronously with an error are 
> bridged using a tuple:
> 
> func doSomething(foo: Foo, completionHandler: (Result<(Bar, Baz)>) -> ())
> 
> and are used like this:
> 
> doSomething(foo: foo) { result in
>       switch result {
>       case let .success(bar, baz):
>               // handle success
>       case let .error(error):
>               self.handleError(error)
>       }
> }
> 
> 4. Methods that return only an error and nothing else are bridged as they are 
> currently, with the exception of bridging NSError to Error as in SE-0112:
> 
> func doSomething(foo: Foo, completionHandler: (Error?) -> ())
> 
> and are used as they currently are:
> 
> doSomething(foo: foo) { error in
>       if let error = error {
>               // handle error
>       } else {
>               // handle success
>       }
> }
> 
> 5. For the case in part 2, the bridge works much like the do/try/catch 
> mechanism. If the first parameter is non-nil, it is returned inside the 
> .success case. If it is nil, then the error is returned inside the .error 
> case if it is non-nil, and otherwise _NilError is returned in the .error case.
> 
> 6. For the case in part 3, in which there are multiple return values, the 
> same pattern is followed, with the exception that we introduce a new 
> Objective-C annotation. I am provisionally naming this annotation 
> NS_REQUIRED_RETURN_VALUE, but the developer team can of course rename this 
> annotation to whatever they find appropriate. All parameters annotated with 
> NS_REQUIRED RETURN_VALUE will be required to be non-nil in order to avoid 
> triggering the error case. Parameters not annotated with NS_REQUIRED 
> RETURN_VALUE will be inserted into the tuple as optionals. If there are no 
> parameters annotated with NS_REQUIRED RETURN_VALUE, the first parameter will 
> be implicitly annotated as such. This allows asynchronous APIs to continue to 
> return optional secondary values if needed.
> 
> Thus, the following API:
> 
> - (void)doSomethingWithFoo: (Foo *)foo completionHandler: (void (^)(Bar * 
> _Nullable NS_REQUIRED_RETURN_VALUE, Baz * _Nullable NS_REQUIRED_RETURN_VALUE, 
> NSError * _Nullable))completionHandler;
> 
> is bridged as:
> 
> func doSomething(foo: Foo, completionHandler: (Result<(Bar, Baz)>) -> ())
> 
> returning .success only if both the Bar and Baz parameters are non-nil, 
> whereas this API:
> 
> - (void)doSomethingWithFoo: (Foo *)foo completionHandler: (void (^)(Bar * 
> _Nullable NS_REQUIRED_RETURN_VALUE, Baz * _Nullable, NSError * 
> _Nullable))completionHandler;
> 
> is bridged as:
> 
> func doSomething(foo: Foo, completionHandler: (Result<(Bar, Baz?)>) -> ())
> 
> returning .success whenever the Bar parameter is nil. An API containing no 
> parameter annotated with NS_REQUIRED_RETURN_VALUE will be bridged the same as 
> above.
> 
> FUTURE DIRECTIONS:
> 
> In the future, an asynchronous API returning a Result could be bridged to an 
> async function, should those be added in the future, using the semantics of 
> the do/try/catch mechanism. The bridging would be additive, similarly to how 
> Objective-C properties declared via manually written accessor methods can 
> nonetheless be accessed via the dot syntax. Thus,
> 
> func doSomething(_ completionHandler: (Result<Foo>) -> ())
> 
> could be used as if it were declared like this:
> 
> async func doSomething() throws -> Foo
> 
> and could be used like so:
> 
> async func doSomethingBigger() {
>       do {
>               let foo = try await doSomething()
> 
>               // do something with foo
>       } catch {
>               // handle the error
>       }
> }
> 
> making asynchronous APIs convenient to write indeed.
> 
> ALTERNATIVES CONSIDERED:
> 
> Leaving the somewhat ambiguous situation as is.
> 
> Charles
> 
> _______________________________________________
> 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

Reply via email to