Save 37% off PRO during our Black Friday Sale! »

Back to the Futures

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.

C0eafab7106ab63b8db4025e57c1a8d2?s=128

Javier Soto

March 21, 2015
Tweet

Transcript

  1. Back to the Futures — @Javi "Back to the Futures"

    - Javier Soto. March 2015 1
  2. 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
  3. Traditional asynchronous code "Back to the Futures" - Javier Soto.

    March 2015 3
  4. 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
  5. Traditional asynchronous code "Back to the Futures" - Javier Soto.

    March 2015 5
  6. 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
  7. 4 (.Some, .Some) 4 (.None, .None) "Back to the Futures"

    - Javier Soto. March 2015 7
  8. 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
  9. 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
  10. "Back to the Futures" - Javier Soto. March 2015 10

  11. "Back to the Futures" - Javier Soto. March 2015 11

  12. "Back to the Futures" - Javier Soto. March 2015 12

  13. 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
  14. NSError Alternative enum NoError: ErrorType { } let error =

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

    } "Back to the Futures" - Javier Soto. March 2015 15
  16. Say hello to Future<T, E> "Back to the Futures" -

    Javier Soto. March 2015 16
  17. 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
  18. 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
  19. 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
  20. Future.map(): transforming the computed value "Back to the Futures" -

    Javier Soto. March 2015 20
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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
  30. Future.andThen(): concatenating async work "Back to the Futures" - Javier

    Soto. March 2015 30
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. Future.andThen(): concatenating async work case .Success(let value): let nextFuture =

    f(value) "Back to the Futures" - Javier Soto. March 2015 36
  37. 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
  38. Future.andThen(): concatenating async work case .Error(let error): completion(Result.Error(error)) "Back to

    the Futures" - Javier Soto. March 2015 38
  39. 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
  40. Recap "Back to the Futures" - Javier Soto. March 2015

    40
  41. 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
  42. ... 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
  43. 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
  44. ReactiveCocoa Signals > Futures "Back to the Futures" - Javier

    Soto. March 2015 44
  45. 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
  46. Thanks! Questions? "Back to the Futures" - Javier Soto. March

    2015 46