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

Result Driven Development

Result Driven Development

Functional Swift Conferencer 2015

Brian Partridge

December 13, 2015
Tweet

More Decks by Brian Partridge

Other Decks in Technology

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