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

Result Driven Development

Result Driven Development

Functional Swift Conferencer 2015

4e264a4ba35b07be0661565fa5e75b81?s=128

Brian Partridge

December 13, 2015
Tweet

Transcript

  1. Result Driven Development BRIAN PARTRIDGE Square Register Team

  2. Agenda • What is Result? • Result with HTTP Requests

    • Result Handling Pipelines • Parsing Using Generics
  3. What is Result?

  4. enum Optional<T> { case None case Some(T) init() init(_ some:

    T) }
  5. protocol ResultType { typealias Value init(value: Value) init(error: ErrorType) var

    value: Value? { get } var error: ErrorType? { get } }
  6. enum Result<T>: ResultType { typealias Value = T case Success(T)

    case Failure(ErrorType) }
  7. Why?

  8. try?

  9. (Widget?, NSError?) -> ()

  10. (Result<Widget>) -> ()

  11. enum Optional<T> { … func map<U>(f: (Wrapped) -> U) ->

    U? func flatMap<U>(f: (Wrapped) -> U?) -> U? }
  12. protocol ResultType { … func map<U>(f: (Value) -> U) ->

    Result<U> func flatMap<U>(f: (Value) -> Result<U>) -> Result<U> } enum Optional<T> { … func map<U>(f: (Wrapped) -> U) -> U? func flatMap<U>(f: (Wrapped) -> U?) -> U? }
  13. Result with HTTP Requests

  14. func executeRequest(request: …, session: …, completion: (NSData?, NSError?)->()) { session.dataTaskWithRequest(request,

    completionHandler: { (data, response, error) in }).resume() }
  15. func executeRequest(request: …, session: …, completion: (NSData?, NSError?)->()) { session.dataTaskWithRequest(request,

    completionHandler: { (data, response, error) in if let error = error { completion(nil, error) return } }).resume() }
  16. func executeRequest(request: …, session: …, completion: (NSData?, NSError?)->()) { session.dataTaskWithRequest(request,

    completionHandler: { (data, response, error) in if let error = error { completion(nil, error) return } guard let httpResponse = response as? NSHTTPURLResponse else { fatalError(…) } if httpResponse.statusCode != 200 { completion(nil, HTTPError()) return } }).resume() }
  17. func executeRequest(request: …, session: …, completion: (NSData?, NSError?)->()) { session.dataTaskWithRequest(request,

    completionHandler: { (data, response, error) in if let error = error { completion(nil, error) return } guard let httpResponse = response as? NSHTTPURLResponse else { fatalError(…) } if httpResponse.statusCode != 200 { completion(nil, HTTPError()) return } guard let data = data else { completion(nil, BadDataError()) return } }).resume() }
  18. func executeRequest(request: …, session: …, completion: (NSData?, NSError?)->()) { session.dataTaskWithRequest(request,

    completionHandler: { (data, response, error) in if let error = error { completion(nil, error) return } guard let httpResponse = response as? NSHTTPURLResponse else { fatalError(…) } if httpResponse.statusCode != 200 { completion(nil, HTTPError()) return } guard let data = data else { completion(nil, BadDataError()) return } completion(data, nil) }).resume() }
  19. func executeRequest(request: …, session: …, completion: (NSData?, NSError?)->()) { session.dataTaskWithRequest(request,

    completionHandler: { (data, response, error) in if let error = error { completion(nil, error) return } guard let httpResponse = response as? NSHTTPURLResponse else { fatalError(…) } if httpResponse.statusCode != 200 { completion(nil, HTTPError()) return } guard let data = data else { completion(nil, BadDataError()) return } completion(data, nil) }).resume() }
  20. func executeRequest(request: …, session: …, completion: (NSData?, NSError?)->()) { session.dataTaskWithRequest(request,

    completionHandler: { (data, response, error) in if let error = error { completion(nil, error) return } guard let httpResponse = response as? NSHTTPURLResponse else { fatalError(…) } if httpResponse.statusCode != 200 { completion(nil, HTTPError()) return } guard let data = data else { completion(nil, BadDataError()) return } completion(data, nil) }).resume() }
  21. func executeRequest(request: …, session: …, completion: (Result<NSData>)->()) { session.dataTaskWithRequest(request, completionHandler:

    { (data, response, error) in if let error = error { completion(.Failure(error: error)) return } guard let httpResponse = response as? NSHTTPURLResponse else { fatalError(…) } if httpResponse.statusCode != 200 { completion(.Failure(error: HTTPError())) return } guard let data = data else { completion(.Failure(error:BadDataError())) return } completion(.Success(value: data)) }).resume() }
  22. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { result in } }
  23. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { result in switch result { case let .Failure(error): completion(.Failure(error)) case let .Success(data): let widget = Widget(data: result.value) completion(.Success(widget)) } } }
  24. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in let widgetResult = httpResult.map { Widget(data: $0) } completion(widgetResult) } }
  25. Result Handling Pipelines

  26. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in switch result { case let .Failure(error): completion(.Failure(error)) case let .Success(data): } } }
  27. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in switch result { case let .Failure(error): completion(.Failure(error)) case let .Success(data): let json = // Parse JSON } } }
  28. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in switch result { case let .Failure(error): completion(.Failure(error)) case let .Success(data): let json = // Parse JSON let widget = // Instantiate Widget } } }
  29. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in switch result { case let .Failure(error): completion(.Failure(error)) case let .Success(data): let json = // Parse JSON let widget = // Instantiate Widget completion(.Success(widget)) } } }
  30. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in switch result { case let .Failure(error): completion(.Failure(error)) case let .Success(data): let json = // Parse JSON // Handle failures let widget = // Instantiate Widget // Handle failures completion(.Success(widget)) } } }
  31. func parseJSONData(data: NSData) -> Result<NSDictionary> func parseWidgetJSON(json: NSDictionary) -> Result<Widget>

  32. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in let jsonResult = httpResult.flatMap(parseJSONData) let widgetResult = jsonResult.flatMap(parseWidgetJSON) completion(widgetResult) } }
  33. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in let result = httpResult .flatMap(parseJSONData) .flatMap(parseWidgetJSON) completion(result) } }
  34. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in let result = httpResult // HTTP 500 .flatMap(parseJSONData) .flatMap(parseWidgetJSON) completion(result) // HTTP 500 } }
  35. func fetchLatestWidget(session: …, completion: (Result<Widget>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in let result = httpResult .flatMap(parseJSONData) // JSON Error .flatMap(parseWidgetJSON) completion(result) // JSON Error } }
  36. Pipeline Benefits • Localized Data Transformation • Smaller Functions •

    More Testable
  37. Parsing Using Generics

  38. protocol PBMessageType { init(data: NSData) } class CompleteBillRequest: PBMessageType {

    let billID: String let completionDate: NSDate } class CompleteBillResponse: PBMessageType { let status: Status let bill: Bill } class Status: PBMessageType { let success: Bool let localizedDescription: String? }
  39. protocol PBMessageType { init(data: NSData) } class CompleteBillRequest: PBMessageType {

    let billID: String let completionDate: NSDate } class CompleteBillResponse: PBMessageType { let status: Status let bill: Bill } class Status: PBMessageType { let success: Bool let localizedDescription: String? }
  40. protocol PBMessageType { init(data: NSData) } class CompleteBillRequest: PBMessageType {

    let billID: String let completionDate: NSDate } class CompleteBillResponse: PBMessageType { let status: Status let bill: Bill } class Status: PBMessageType { let success: Bool let localizedDescription: String? }
  41. func parseProtoResponseData<T>(data: NSData) -> Result<T> { }

  42. func parseProtoResponseData<T>(data: NSData) -> Result<T> { let object = T(data:

    data) }
  43. func parseProtoResponseData<T>(data: NSData) -> Result<T> { let object = T(data:

    data) if object.status.success { return .Success(value: object) } else { return .Failure(StatusError()) } }
  44. func parseProtoResponseData<T>(data: NSData) -> Result<T> { let object = T(data:

    data) // 'T' cannot be constructed if object.status.success { return .Success(value: object) } else { return .Failure(StatusError()) } }
  45. func parseProtoResponseData<T:PBMessageType>(data: NSData) -> Result<T> { let object = T(data:

    data) if object.status.success { return .Success(value: object) } else { return .Failure(StatusError()) } }
  46. func parseProtoResponseData<T:PBMessageType>(data: NSData) -> Result<T> { let object = T(data:

    data) if object.status.success { // Value of type 'T' has no member 'status' return .Success(value: object) } else { return .Failure(StatusError()) } }
  47. protocol PBResponseMessageType { let status: Status } extension CompleteBillResponse: PBResponseMessageType

    {} func parseProtoResponseData<T: PBResponseMessageType>(data: NSData) -> Result<T> { … }
  48. protocol PBResponseMessageType { let status: Status } extension CompleteBillResponse: PBResponseMessageType

    {} func parseProtoResponseData<T: PBResponseMessageType>(data: NSData) -> Result<T> { … }
  49. protocol PBResponseMessageType { let status: Status } extension CompleteBillResponse: PBResponseMessageType

    {} func parseProtoResponseData<T: PBResponseMessageType>(data: NSData) -> Result<T> { … }
  50. let parsedResult = parseProtoResponseData(data) // Generic parameter 'T' could not

    be inferred.
  51. let parsedResult: Result<CompleteBillResponse> = parseProtoResponseData(data)

  52. let parsedResult: Result<CompleteBillResponse> = parseProtoResponseData(data) // T is CompleteBillResponse

  53. func completeBill(session: …, completion: (Result<Bill>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in } }
  54. func completeBill(session: …, completion: (Result<Bill>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in let completeBillResult: Result<CompleteBillResponse> } }
  55. func completeBill(session: …, completion: (Result<Bill>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in let completeBillResult: Result<CompleteBillResponse> completeBillResult = httpResult.flatMap(parseProtoResponseData) } }
  56. func completeBill(session: …, completion: (Result<Bill>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in let completeBillResult: Result<CompleteBillResponse> completeBillResult = httpResult.flatMap(parseProtoResponseData) let billResult = completeBillResult.map { $0.bill } } }
  57. func completeBill(session: …, completion: (Result<Bill>)->()) { let request = ...

    executeRequest(request, session: session) { httpResult in let completeBillResult: Result<CompleteBillResponse> completeBillResult = httpResult.flatMap(parseProtoResponseData) let billResult = completeBillResult.map { $0.bill } completion(billResult) } }
  58. Summary • Clearer Semantics • Pipelining • Generics For Parsing

  59. References Networking With Monads By John Gallagher @ Functional Swift

    Conf 2014 http://2014.funswiftconf.com/speakers/john.html antitypical/Result https://github.com/antitypical/Result
  60. Q&A

  61. bp@squareup.com @brianpartridge