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
