Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Back to the Futures

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

Back to the Futures

Presentation given at Swift Summit 2015 in London on the problems with traditional asynchronous, callback-based APIs in Objective-C, NSError-based error handling, and how we can do both a lot better in Swift, for example, by implementing a simple Future API.

Avatar for Javier Soto

Javier Soto

March 21, 2015
Tweet

More Decks by Javier Soto

Other Decks in Programming

Transcript

  1. Agenda 4 Traditional Asynchronous Code 4 Problems with Traditional Error

    Handling 4 Future 4 Future.map 4 Future.andThen "Back to the Futures" - Javier Soto. March 2015 2
  2. Traditional asynchronous code struct User { let avatarURL: NSURL }

    func requestUserInfo(userID: String, completion: (User?, NSError?) -> ()) func downloadImage(URL: NSURL, completion: (UIImage?, NSError?) -> ()) func loadAvatar(userID: String, completion: (UIImage?, NSError?) -> ()) { requestUserInfo(userID) { user, error in if let user = user { downloadImage(user.avatarURL) { avatar, error in if let avatar = avatar { completion(avatar, nil) } else { completion(nil, error!) } } } else { completion(nil, error!) } } } "Back to the Futures" - Javier Soto. March 2015 4
  3. Traditional asynchronous code func downloadImage(URL: NSURL, completion: (UIImage?, NSError?) ->

    ()) 4 (.Some, .None) 4 (.None, .Some) 4 (.Some, .Some) 4 (.None, .None) "Back to the Futures" - Javier Soto. March 2015 6
  4. Error Handling func downloadImage(URL: NSURL, completion: (UIImage?, NSError?) -> ())

    downloadImage(url) { imageOrNil, errorOrNil in if error = errorOrNil { // What if image is also not nil?? } else if image = imageOrNil { } } "Back to the Futures" - Javier Soto. March 2015 8
  5. Error Handling var error: NSError? let string = NSString(contentsOfFile:path encoding:NSUTF8Encoding

    error:&error) if string == nil { // Oops: if error.code == NSFileReadNoSuchFileError { // ... } else if ... } "Back to the Futures" - Javier Soto. March 2015 9
  6. NSError Alternative protocol ErrorType { } enum UserInfoErrorDomain: ErrorType {

    case UserDoesNotExist case UserRequestFailure(reason: String) case NetworkRequestFailure(reason: String) } extension NSError: ErrorType { } "Back to the Futures" - Javier Soto. March 2015 13
  7. NSError Alternative enum NoError: ErrorType { } let error =

    NoError(?) "Back to the Futures" - Javier Soto. March 2015 14
  8. Result enum Result<T, E: ErrorType> { case Success(T) case Error(E)

    } "Back to the Futures" - Javier Soto. March 2015 15
  9. Futures 4 Encapsulate a deferred computation. 4 Treat values that

    incur a delay to be retrieved as if they were regular values. 4 Allow us to treat errors as first class citizens. 4 Easily composable. "Back to the Futures" - Javier Soto. March 2015 17
  10. Future struct Future<T, E: ErrorType> { typealias ResultType = Result<T,

    E> typealias Completion = ResultType -> () typealias AsyncOperation = Completion -> () private let operation: AsyncOperation } "Back to the Futures" - Javier Soto. March 2015 18
  11. Future struct Future<T, E: ErrorType> { init(operation: AsyncOperation) { self.operation

    = operation } func start(completion: Completion) { self.operation() { result in completion(result) } } } "Back to the Futures" - Javier Soto. March 2015 19
  12. Future.map(): transforming the computed value struct User { let avatarURL:

    NSURL } func requestUserInfo(userID: String) -> Future<User, ErrorDomain> func requestUserAvatarURL(userID: String) -> Future<NSURL, ErrorDomain> { return requestUserInfo(userID) .map { $0.avatarURL } } "Back to the Futures" - Javier Soto. March 2015 21
  13. Future.map(): transforming the computed value struct Future<T, E: ErrorType> {

    func map<U>(f: T -> U) -> Future<U, E> } "Back to the Futures" - Javier Soto. March 2015 22
  14. map() in other types struct Array<T> { func map<U>(f: T

    -> U) -> [U] } enum Optional<T> { func map<U>(f: T -> U) -> U? } struct Future<T, E: ErrorType> { func map<U>(f: T -> U) -> Future<U, E> } "Back to the Futures" - Javier Soto. March 2015 23
  15. Future.map(): transforming the computed value func map<U>(f: T -> U)

    -> Future<U, E> { // Return a new Future w/ a new operation... return Future<U, E>(operation: { completion in } } "Back to the Futures" - Javier Soto. March 2015 24
  16. Future.map(): transforming the computed value func map<U>(f: T -> U)

    -> Future<U, E> { return Future<U, E>(operation: { completion in // Retrieve the value from self... self.start { result in } } } "Back to the Futures" - Javier Soto. March 2015 25
  17. Future.map(): transforming the computed value func map<U>(f: T -> U)

    -> Future<U, E> { return Future<U, E>(operation: { completion in self.start { result in // Consider .Success and .Error... switch result { } } }) } "Back to the Futures" - Javier Soto. March 2015 26
  18. Future.map(): transforming the computed value case .Success(let value): // Call

    completion with the transformed value completion(Result.Success(f(value))) "Back to the Futures" - Javier Soto. March 2015 27
  19. Future.map(): transforming the computed value case .Error(let error): // We

    didn't get a value: no transformation completion(Result.Error(error)) "Back to the Futures" - Javier Soto. March 2015 28
  20. Future.map(): transforming the computed value func map<U>(f: T -> U)

    -> Future<U, E> { return Future<U, E>(operation: { completion in self.start { result in switch result { case .Success(let value): completion(Result.Success(f(value))) case .Error(let error): completion(Result.Error(error)) } } }) } "Back to the Futures" - Javier Soto. March 2015 29
  21. Future.andThen(): concatenating async work func requestUserAvatarURL(userID: String) -> Future<NSURL, ErrorDomain>

    func downloadImage(URL: NSURL) -> Future<UIImage, ErrorDomain> func downloadUserAvatar(userID: String) -> Future<UIImage, ErrorDomain> { return requestUserAvatarURL(userID) .andThen(downloadImage) } "Back to the Futures" - Javier Soto. March 2015 31
  22. Future.andThen(): concatenating async work struct Future<T, E: ErrorType> { func

    andThen<U>(f: T -> Future<U, E>) -> Future<U, E> } "Back to the Futures" - Javier Soto. March 2015 32
  23. Future.andThen(): concatenating async work func andThen<U>(f: T -> Future<U, E>)

    -> Future<U, E> { return Future<U, E>(operation: { completion in } } "Back to the Futures" - Javier Soto. March 2015 33
  24. Future.andThen(): concatenating async work func andThen<U>(f: T -> Future<U, E>)

    -> Future<U, E> { return Future<U, E>(operation: { completion in self.start { firstFutureResult in } } } "Back to the Futures" - Javier Soto. March 2015 34
  25. Future.andThen(): concatenating async work func andThen<U>(f: T -> Future<U, E>)

    -> Future<U, E> { return Future<U, E>(operation: { completion in self.start { firstFutureResult in switch firstFutureResult { case .Success(let value): // ... case .Error(let error): // ... } } } } "Back to the Futures" - Javier Soto. March 2015 35
  26. Future.andThen(): concatenating async work case .Success(let value): let nextFuture =

    f(value) "Back to the Futures" - Javier Soto. March 2015 36
  27. Future.andThen(): concatenating async work case .Success(let value): let nextFuture =

    f(value) nextFuture.start { finalResult in completion(finalResult) } "Back to the Futures" - Javier Soto. March 2015 37
  28. Future.andThen(): concatenating async work func andThen<U>(f: T -> Future<U, E>)

    -> Future<U, E> { return Future<U, E>(operation: { completion in self.start { firstFutureResult in switch firstFutureResult { case .Success(let value): f(value).start(completion) case .Error(let error): completion(Result.Error(error)) } } }) } "Back to the Futures" - Javier Soto. March 2015 39
  29. We went from this... func loadAvatar(userID: String, completion: (UIImage?, NSError?)

    -> ()) { requestUserInfo(userID) { user, error in if let user = user { downloadImage(user.avatarURL) { avatar, error in if let avatar = avatar { completion(avatar, nil) } else { completion(nil, error) } } } else { completion(nil, error) } } } "Back to the Futures" - Javier Soto. March 2015 41
  30. ... To this func requestUserInfo(userID: String) -> Future<User, UserInfoErrorDomain> func

    downloadImage(URL: NSURL) -> Future<UIImage, UserInfoErrorDomain> func loadAvatar(userID: String) -> Future<UIImage, UserInfoErrorDomain> { return requestUserInfo(userID) .map { $0.avatarURL } .andThen(downloadImage) } "Back to the Futures" - Javier Soto. March 2015 42
  31. Limitations of Futures 4 Only represent computation to retrieve one

    value. 4 Can't encapsulate streams of values. 4 Only consumer (pull) driven, not producer (push) driven. "Back to the Futures" - Javier Soto. March 2015 43
  32. Follow-up Resources 4 Playground: https://github.com/JaviSoto/Talks 4 ReactiveCocoa: https://github.com/ReactiveCocoa/ ReactiveCocoa 4

    Railway Oriented Programming: http:// fsharpforfunandprofit.com/posts/recipe-part2/ "Back to the Futures" - Javier Soto. March 2015 45