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
