Is there a way I can make that clearer than putting it in a section labeled 
“FUTURE DIRECTIONS”?

Charles

> On Jul 14, 2016, at 5:52 PM, Dan Appel <[email protected]> wrote:
> 
> Yes, it should be made clear that the 'async'/'await' suggestions are future 
> directions, which are more to show how flexible the design is rather than 
> actually be a part of the implementation.
> 
> On Thu, Jul 14, 2016 at 3:30 PM Charles Srstka via swift-evolution 
> <[email protected] <mailto:[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] 
> > <mailto:[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] <mailto:[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] <mailto:[email protected]>
> >> https://lists.swift.org/mailman/listinfo/swift-evolution 
> >> <https://lists.swift.org/mailman/listinfo/swift-evolution>
> >
> 
> _______________________________________________
> swift-evolution mailing list
> [email protected] <mailto:[email protected]>
> https://lists.swift.org/mailman/listinfo/swift-evolution 
> <https://lists.swift.org/mailman/listinfo/swift-evolution>
> -- 
> Dan Appel

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

Reply via email to