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

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.

Javier Soto

March 21, 2015
Tweet

More Decks by Javier Soto

Other Decks in Programming

Transcript

  1. Back to the Futures
    — @Javi
    "Back to the Futures" - Javier Soto. March 2015 1

    View full-size slide

  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

    View full-size slide

  3. Traditional asynchronous code
    "Back to the Futures" - Javier Soto. March 2015 3

    View full-size slide

  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

    View full-size slide

  5. Traditional asynchronous code
    "Back to the Futures" - Javier Soto. March 2015 5

    View full-size slide

  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

    View full-size slide

  7. 4 (.Some, .Some)
    4 (.None, .None)
    "Back to the Futures" - Javier Soto. March 2015 7

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  14. NSError Alternative
    enum NoError: ErrorType { }
    let error = NoError(?)
    "Back to the Futures" - Javier Soto. March 2015 14

    View full-size slide

  15. Result
    enum Result {
    case Success(T)
    case Error(E)
    }
    "Back to the Futures" - Javier Soto. March 2015 15

    View full-size slide

  16. Say hello to
    Future
    "Back to the Futures" - Javier Soto. March 2015 16

    View full-size slide

  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

    View full-size slide

  18. Future
    struct Future {
    typealias ResultType = Result
    typealias Completion = ResultType -> ()
    typealias AsyncOperation = Completion -> ()
    private let operation: AsyncOperation
    }
    "Back to the Futures" - Javier Soto. March 2015 18

    View full-size slide

  19. Future
    struct Future {
    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

    View full-size slide

  20. Future.map(): transforming the computed value
    "Back to the Futures" - Javier Soto. March 2015 20

    View full-size slide

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

    View full-size slide

  22. Future.map(): transforming the computed value
    struct Future {
    func map(f: T -> U) -> Future
    }
    "Back to the Futures" - Javier Soto. March 2015 22

    View full-size slide

  23. map() in other types
    struct Array {
    func map(f: T -> U) -> [U]
    }
    enum Optional {
    func map(f: T -> U) -> U?
    }
    struct Future {
    func map(f: T -> U) -> Future
    }
    "Back to the Futures" - Javier Soto. March 2015 23

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  26. Future.map(): transforming the computed value
    func map(f: T -> U) -> Future {
    return Future(operation: { completion in
    self.start { result in
    // Consider .Success and .Error...
    switch result {
    }
    }
    })
    }
    "Back to the Futures" - Javier Soto. March 2015 26

    View full-size slide

  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

    View full-size slide

  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

    View full-size slide

  29. Future.map(): transforming the computed value
    func map(f: T -> U) -> Future {
    return Future(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

    View full-size slide

  30. Future.andThen(): concatenating async work
    "Back to the Futures" - Javier Soto. March 2015 30

    View full-size slide

  31. Future.andThen(): concatenating async work
    func requestUserAvatarURL(userID: String) -> Future
    func downloadImage(URL: NSURL) -> Future
    func downloadUserAvatar(userID: String) -> Future {
    return requestUserAvatarURL(userID)
    .andThen(downloadImage)
    }
    "Back to the Futures" - Javier Soto. March 2015 31

    View full-size slide

  32. Future.andThen(): concatenating async work
    struct Future {
    func andThen(f: T -> Future) -> Future
    }
    "Back to the Futures" - Javier Soto. March 2015 32

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  35. Future.andThen(): concatenating async work
    func andThen(f: T -> Future) -> Future {
    return Future(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

    View full-size slide

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

    View full-size slide

  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

    View full-size slide

  38. Future.andThen(): concatenating async work
    case .Error(let error):
    completion(Result.Error(error))
    "Back to the Futures" - Javier Soto. March 2015 38

    View full-size slide

  39. Future.andThen(): concatenating async work
    func andThen(f: T -> Future) -> Future {
    return Future(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

    View full-size slide

  40. Recap
    "Back to the Futures" - Javier Soto. March 2015 40

    View full-size slide

  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

    View full-size slide

  42. ... To this
    func requestUserInfo(userID: String) -> Future
    func downloadImage(URL: NSURL) -> Future
    func loadAvatar(userID: String) -> Future {
    return requestUserInfo(userID)
    .map { $0.avatarURL }
    .andThen(downloadImage)
    }
    "Back to the Futures" - Javier Soto. March 2015 42

    View full-size slide

  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

    View full-size slide

  44. ReactiveCocoa
    Signals > Futures
    "Back to the Futures" - Javier Soto. March 2015 44

    View full-size slide

  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

    View full-size slide

  46. Thanks!
    Questions?
    "Back to the Futures" - Javier Soto. March 2015 46

    View full-size slide