Won’t that involve source breakage, though? The grammar for calling 
asynchronous APIs will have changed.

Charles

> On Jul 14, 2016, at 11:54 PM, Chris Lattner <[email protected]> wrote:
> 
> When/if we get an async/await like feature, of course we’ll try to pull 
> completion handlers automatically into the model.  We can do that post swift 
> 3.
> 
> -Chris
> 
>> On Jul 14, 2016, at 3:30 PM, Charles Srstka via swift-evolution 
>> <[email protected]> wrote:
>> 
>> Right, but since this would affect the Obj-C importer and thus would be a 
>> source-breaking change, it would probably not be possible anymore after 
>> Swift 3.
>> 
>> Charles
>> 
>>> On Jul 14, 2016, at 4:57 PM, Dan Stenmark <[email protected]> 
>>> wrote:
>>> 
>>> 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
> 

_______________________________________________
swift-evolution mailing list
[email protected]
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to