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

Result Oriented Development

Saul Mora
September 02, 2016

Result Oriented Development

When I last talked about functional programming, we saw by using small, micro functions, a nasty, complex and hard to track function could eventually be written as a pipeline of smaller functions. But using only optionals to pipe functions together isn’t enough to take full advantage of this technique. With the help of a small, but useful Monad called Result (or Either) you can take your functional programming powers to the next level.

Saul Mora

September 02, 2016
Tweet

More Decks by Saul Mora

Other Decks in Programming

Transcript

  1. func old_and_busted_expired(fileURL: NSURL) -> Bool { let fileManager = NSFileManager()

    if let filePath = fileURL.path { if fileManager.fileExistsAtPath(filePath) { var error: NSError? let fileAttributes = fileManager.attributesOfItemAtPath(filePath, error: &error) if let fileAttributes = fileAttributes { if let creationDate = fileAttributes[NSFileModificationDate] as? NSDate { return creationDate.isBefore(NSDate.oneDayAgo()) } } else { NSLog("No file attributes \(filePath)") } } } return true }
  2. func expired(fileURL: NSURL) -> Bool { return fileURL.path >>- fileExists

    >>- retrieveFileAttributes >>- extractCreationDate >>- checkExpired ?? true }
  3. let result = doSomethingFun() switch result { case .success(let funThing):

    print("Had fun: \(funThing)") case .error: print("Error having fun ") }
  4. /// A type that can represent either a `Wrapped` value

    or `nil`, the absence /// of a value. public enum Optional<Wrapped> : _Reflectable, NilLiteralConvertible { case None case Some(Wrapped) /// Construct a `nil` instance. public init() /// Construct a non-`nil` instance that stores `some`. public init(_ some: Wrapped) /// If `self == nil`, returns `nil`. Otherwise, returns `f(self!)`. @warn_unused_result public func map<U>(@noescape f: (Wrapped) throws -> U) rethrows -> U? /// Returns `nil` if `self` is `nil`, `f(self!)` otherwise. @warn_unused_result public func flatMap<U>(@noescape f: (Wrapped) throws -> U?) rethrows -> U? /// Create an instance initialized with `nil`. public init(nilLiteral: ()) }
  5. map

  6. func map<U>(_ transform: T -> U) -> U? { switch

    self { case .some(let x): return .some(transform(x)) case .none: return .none } }
  7. func map<U>(_ transform: T -> Result<U>) -> Result<U> { switch

    self { case .success(let value): return transform(value) case .failure(let error): return Result<T>.failure(error) } }
  8. enum Result<T> { case success(_ value: T) case failure(_ error:

    Error) func map<U>(_ transform: T -> U) -> U { switch self { case .success(let value): return transform(value) case .failure(let error): return Result<T>.failure(error) } } }
  9. let result: Result<String> = doSomething() result .map { value in

    print("Had fun: \(value)") } .mapError { error -> NSError in print("Error having fun: \(error)") } let result: Result<String> = doSomething() result .map { value in print("Had fun: \(value)") }
  10. func apiRequest<A> ( modifyRequest: NSMutableURLRequest -> (), baseURL: NSURL, resource:

    Resource<A>, failure: (Reason, NSData?) -> (), completion: A -> () )
  11. public protocol HTTPResourceProtocol { associatedtype ResultType associatedtype ErrorType: HTTPResourceError var

    path: String { get } var method: HTTPMethod { get } var queryParameters: [String: String] { get } var parse: Result<ResourceDataType, HTTPResponseError> { get } }
  12. public typealias ResourceParseFunction<ResourceDataType> = (Data) -> Result<ResourceDataType, HTTPResponseError> public protocol

    HTTPResourceProtocol { associatedtype ResultType associatedtype ErrorType: HTTPResourceError var path: String { get } var method: HTTPMethod { get } var queryParameters: [String: String] { get } var parse: ResourceParseFunction<ResultType> { get } }
  13. • validate response code • validate/preprocess data • deserialize JSON

    from data • decode JSON objects • call completion handler
  14. func completionHandlerForRequest<R: HTTPResource>( resource: R, validate: ResponseValidationFunction, completion: @escaping (Result<R.RequestedType>)

    -> Void ) -> (Data?, URLResponse?, Error?) -> Void { return { (data, response, error) in _ = Result(response as? HTTPURLResponse, failWith: .InvalidResponseType) >>- validateResponse(error) >>- validate(data) >>- resource.parse >>- completion } } return { (data, response, error) in _ = Result(response as? HTTPURLResponse, failWith: .InvalidResponseType) >>- validateResponse(error) >>- validate(data) >>- resource.parse >>- completion }
  15. return { (data, response, error) in _ = Result(response as?

    HTTPURLResponse, failWith: .InvalidResponseType) >>- validateResponse(error) >>- validate(data) >>- resource.parse >>- completion }
  16. return { (data, response, error) in _ = Result(response as?

    HTTPURLResponse, failWith: .InvalidResponseType) >>- validateResponse(error) >>- validate(data) >>- resource.parse >>- completion }
  17. validateResponse : (HTTPURLResponse?) -> Result<HTTPURLResponse> customValidate : (HTTPURLResponse) -> Result<Data>

    response.parse : (Data) -> Result<ResourceType> completion : (Result<ResourceType>) -> Void
  18. return { (data, response, error) in _ = Result(response as?

    HTTPURLResponse, failWith: .InvalidResponseType) >>- validateResponse(error) >>- validate(data) >>- resource.parse >>- completion }
  19. func siteGauge(siteID: SiteID) -> HTTPResource<Gauge> { let path = "gauges/\(siteID)"

    return HTTPResource(path: path, parse: parse(rootKey: "gauge")) }
  20. request(resource: siteGauge(siteID: "my_site_id")) { result in result.map { site in

    print("Found \(site)") } .mapError { error -> HTTPResourceError in print("Error requesting resource: \(error)") return error } }
  21. request(resource: siteGauge(siteID: "my_site_id")) { result in switch result { case

    .success(let site): print("Found \(site)”) case .failure(let error): print("Error getting site: \(error)") } }