IIRC, one of the main goals in Swift 3 was to further stabilize the language 
spec and *minimize* breakage in future versions, not eliminate potential 
breakage altogether.  Considering the numerous directions Native Concurrency 
can take (Go-style green thread scheduling, async-await, continuation handler 
transforms, etc), it would be much better if that discussion got started first. 
 Asynchronous error handling will likely be a big talking point of said 
discussion anyway.

Dan

> On Jul 14, 2016, at 3:30 PM, Charles Srstka <cocoa...@charlessoft.com> 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 <daniel.j.stenm...@gmail.com> 
>> 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 
>>> <swift-evolution@swift.org> 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
>>> swift-evolution@swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>> 
> 

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Reply via email to