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

A deep dive into the result type — Appdevcon 2019

A deep dive into the result type — Appdevcon 2019

In this presentation—given at Appdevcon march 2019—we take a closer look at Swift 5’s result type and how this functional programming paradigm compares to “regular” imperative error handling.

Tjeerd in 't Veen

March 15, 2019
Tweet

Other Decks in Programming

Transcript

  1. WHAT WE’LL COVER ▸ Swift’s error handling ▸ Result’s purpose

    ▸ Dynamic errors handling + Result ▸ Mixing Result with throwing functions ▸ Functional Combinators ▸ Downsides
  2. SWIFT LOOKS AT FUNCTIONAL PROGRAMMING LANGUAGES ▸ Higher-order functions ▸

    Optional (Haskell’s Maybe type) ▸ Algebraic data types (structs, tuples, and enums)
  3. defer { findShelter() } do { try pressButton() } catch

    ImpendingDoomError.explosion { // handle specific error } catch { // handle anything else }
  4. let error: NSError = ImpendingDoomError.explosion as NSError print(error) // Error

    Domain=com.impendingdoom Code=500 "(null)" UserInfo={Oh no=We are doomed}
  5. enum ImpendingDoomError: CustomNSError { case alienAttack case explosion case volcanicEruption

    static var errorDomain: String { return "com.impendingdoom" } /// The error code within the given domain. var errorCode: Int { return 500 } /// The user-info dictionary. var errorUserInfo: [String : Any] { return ["Oh no" : "We are doomed"] } }
  6. defer { findShelter() } do { try pressButton() } catch

    ImpendingDoomError.explosion { // handle specific error } catch { // handle anything else }
  7. let url = URL(string: “https://itunes.apple.com/search?term=harry%20potter")! let task = URLSession.shared.dataTask(with: url)

    { (data, response, error) -> Void in if let data = data, error == nil { store(data: data) } else if let error = error { handle(error: error) } else { // both data and error are nil, what now? fatalError("Shouldn't come here ") } } task.resume()
  8. let url = URL(string: “https://itunes.apple.com/search?term=harry%20potter")! let task = URLSession.shared.dataTask(with: url)

    { (data, response, error) -> Void in if let data = data, error == nil { store(data: data) } else if let error = error { handle(error: error) } else { // both data and error are nil, what now? fatalError("Shouldn't come here ") } } task.resume()
  9. let url = URL(string: “https://itunes.apple.com/search?term=harry%20potter")! let task = URLSession.shared.dataTask(with: url)

    { (data, response, error) -> Void in if let data = data, error == nil { store(data: data) } else if let error = error { handle(error: error) } else { // both data and error are nil, what now? fatalError("Shouldn't come here ") } } task.resume()
  10. let url = URL(string: “https://itunes.apple.com/search?term=harry%20potter")! let task = URLSession.shared.dataTask(with: url)

    { (data, response, error) -> Void in if let data = data, error == nil { store(data: data) } else if let error = error { handle(error: error) } else { // both data and error are nil, what now? fatalError("Shouldn't come here ") } } task.resume()
  11. enum Result<Value, Failure: Error> { case success(Value) case failure(Failure) }

    enum Result<Value, Failure> { case success(Value) case failure(Failure) } enum Result<Value> { case success(Value) case error(NSError) case canceled } enum Result<Value> { case success(Value) case failure(Error) }
  12. public enum Result<Success, Failure> where Failure : Error { ///

    A success, storing a `Success` value. case success(Success) /// A failure, storing a `Failure` value. case failure(Failure) // ... rest omitted } public enum Optional<Wrapped> { case none case some(Wrapped) }
  13. let url = URL(string: “https://itunes.apple.com/search?term=harry%20potter")! let task = URLSession.shared.dataTask(with: url)

    { (data, response, error) -> Void in if let data = data, error == nil { store(data: data) } else if let error = error { handle(error: error) } else { // both data and error are nil, what now? fatalError("Shouldn't come here ") } } task.resume()
  14. let url = URL(string: “https://itunes.apple.com/search?term=harry%20potter")! let task = URLSession.shared.dataTask(with: url)

    { (result: Result<Data, Error>) in switch result { case .success(let data): store(data: data) case .failure(let error): handle(error: error) } } task.resume()
  15. extension URLSession { func dataTask(with url: URL, completionHandler: @escaping (Result<Data,

    Error>) -> Void) -> URLSessionDataTask { return dataTask(with: url) { (data, response, error) in switch (data, error) { case let (data?, nil): completionHandler(Result.success(data)) case let (nil, error?): completionHandler(Result.failure(error)) default: fatalError("Shouldn't come here ") } } } }
  16. let pancake: Pancake = try bakePancake(temperature: 110) /// - Throws:

    BakingError func bakePancake(temperature: Int) throws -> Pancake { if temperature < 100 { throw BakingError.uncookedPancake } else if temperature > 300 { throw BakingError.burntPancake } return Pancake() }
  17. let pancake: Result<Pancake, BakingError> = bakePancake(temperature: 110) func bakePancake(temperature: Int)

    -> Result<Pancake, BakingError> { if temperature < 100 { return Result.failure(BakingError.rawPancake) } else if temperature > 300 { return Result.failure(BakingError.burntPancake) } let pancake = Pancake() return Result.success(pancake) }
  18. let pancake: Result<Pancake, Error> = bakePancake(temperature: 110) func bakePancake(temperature: Int)

    -> Result<Pancake, Error> { if temperature < 100 { return Result.failure(BakingError.rawPancake) } else if temperature > 300 { return Result.failure(BakingError.burntPancake) } else if temperature > 500 { return Result.failure(CookingError.kitchenFire) } let pancake = Pancake() return Result.success(pancake) }
  19. typealias GenericResult<T> = Result<T, Error> func bakePancake(temperature: Int) -> GenericResult<Pancake>

    { if temperature < 100 { return Result.failure(BakingError.rawPancake) } else if temperature > 300 { return Result.failure(BakingError.burntPancake) } else if temperature > 500 { return Result.failure(CookingError.kitchenFire) } let pancake = Pancake() return Result.success(pancake) }
  20. func loadFile(resource: String, type: String) -> Result<Data, Error> { if

    let fileURL = Bundle.main.url(forResource: resource, withExtension: type) { } else { return Result.failure(FileError.couldNotFindFile) } }
  21. func loadFile(resource: String, type: String) -> Result<Data, Error> { if

    let fileURL = Bundle.main.url(forResource: resource, withExtension: type) { } else { return Result.failure(FileError.couldNotFindFile) } }
  22. func loadFile(resource: String, type: String) -> Result<Data, Error> { if

    let fileURL = Bundle.main.url(forResource: resource, withExtension: type) { } else { return Result.failure(FileError.couldNotFindFile) } }
  23. func loadFile(resource: String, type: String) -> Result<Data, Error> { if

    let fileURL = Bundle.main.url(forResource: resource, withExtension: type) { } else { return Result.failure(FileError.couldNotFindFile) } } do { let data = try Data(contentsOf: fileURL) return Result.success(data) } catch { return Result.failure(error) }
  24. func loadFile(resource: String, type: String) -> Result<Data, Error> { if

    let fileURL = Bundle.main.url(forResource: resource, withExtension: type) { } else { return Result.failure(FileError.couldNotFindFile) } } do { let data = try Data(contentsOf: fileURL) return Result.success(data) } catch { return Result.failure(error) }
  25. func loadFile(resource: String, type: String) -> Result<Data, Error> { if

    let fileURL = Bundle.main.url(forResource: resource, withExtension: type) { } else { return Result.failure(FileError.couldNotFindFile) } } do { let data = try Data(contentsOf: fileURL) return Result.success(data) } catch { return Result.failure(error) }
  26. func loadFile(resource: String, type: String) -> Result<Data, Error> { if

    let fileURL = Bundle.main.url(forResource: resource, withExtension: type) { } else { return Result.failure(FileError.couldNotFindFile) } } do { let data = try Data(contentsOf: fileURL) return Result.success(data) } catch { return Result.failure(error) }
  27. func loadFile(resource: String, type: String) -> Result<Data, Error> { if

    let fileURL = Bundle.main.url(forResource: resource, withExtension: type) { } else { return Result.failure(FileError.couldNotFindFile) } } return Result(catching: { try Data(contentsOf: fileURL) })
  28. func loadFile(resource: String, type: String) -> Result<Data, Error> { if

    let fileURL = Bundle.main.url(forResource: resource, withExtension: type) { } else { return Result.failure(FileError.couldNotFindFile) } } return Result(catching: { try Data(contentsOf: fileURL) })
  29. func loadWeatherReport() -> Result<WeatherReport, Error> { let result: Result<Data, Error>

    = loadFile(resource: "weather", type: “”) switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } }
  30. func loadWeatherReport() -> Result<WeatherReport, Error> { let result: Result<Data, Error>

    = loadFile(resource: "weather", type: “json”) switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } }
  31. func loadWeatherReport() -> Result<WeatherReport, Error> { let result: Result<Data, Error>

    = loadFile(resource: "weather", type: “json”) switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } }
  32. func loadWeatherReport() -> Result<WeatherReport, Error> { let result: Result<Data, Error>

    = loadFile(resource: "weather", type: “json”) switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } }
  33. func loadWeatherReport() -> Result<WeatherReport, Error> { let result: Result<Data, Error>

    = loadFile(resource: "weather", type: “json”) switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } }
  34. func loadWeatherReport() -> Result<WeatherReport, Error> { let result: Result<Data, Error>

    = loadFile(resource: "weather", type: “json”) switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } }
  35. MAP

  36. func loadWeatherReport() -> Result<WeatherReport, Error> { let result: Result<Data, Error>

    = loadFile(resource: "weather", type: “json”) switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } }
  37. func loadWeatherReport() -> Result<WeatherReport, Error> { let result = loadFile(resource:

    "weather", type: "") switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } } let result: Result<WeatherReport, Error> = loadFile(resource: "weather", type: "json") .map({ (data: Data) -> WeatherReport in WeatherReport(data: data) }) return result
  38. func loadWeatherReport() -> Result<WeatherReport, Error> { let result = loadFile(resource:

    "weather", type: "") switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } } let result: Result<WeatherReport, Error> = loadFile(resource: "weather", type: "json") .map({ (data: Data) -> WeatherReport in WeatherReport(data: data) }) return result
  39. func loadWeatherReport() -> Result<WeatherReport, Error> { let result = loadFile(resource:

    "weather", type: "") switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } } let result: Result<WeatherReport, Error> = loadFile(resource: "weather", type: "json") .map({ (data: Data) -> WeatherReport in WeatherReport(data: data) }) return result
  40. func loadWeatherReport() -> Result<WeatherReport, Error> { let result = loadFile(resource:

    "weather", type: "") switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } } let result: Result<WeatherReport, Error> = loadFile(resource: "weather", type: "json") .map({ (data: Data) -> WeatherReport in WeatherReport(data: data) }) return result
  41. func loadWeatherReport() -> Result<WeatherReport, Error> { let result = loadFile(resource:

    "weather", type: "") switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } } let result: Result<WeatherReport, Error> = loadFile(resource: "weather", type: "json") .map({ (data: Data) -> WeatherReport in WeatherReport(data: data) }) return result
  42. func loadWeatherReport() -> Result<WeatherReport, Error> { let result = loadFile(resource:

    "weather", type: "") switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } } let result: Result<WeatherReport, Error> = loadFile(resource: "weather", type: "json") .map({ (data: Data) -> WeatherReport in WeatherReport(data: data) }) return result
  43. func loadWeatherReport() -> Result<WeatherReport, Error> { let result = loadFile(resource:

    "weather", type: "") switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } } let result: Result<WeatherReport, Error> = loadFile(resource: "weather", type: "json") .map({ (data: Data) -> WeatherReport in WeatherReport(data: data) }) return result
  44. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(WeatherReport.init) return result
  45. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) .map(WeatherReport.init) return result
  46. func loadWeatherReport() -> Result<WeatherReport, Error> { let result = loadFile(resource:

    "weather", type: "") switch result { case .success(let data): let report = WeatherReport(data: data) return Result.success(report) case .failure(let error): return Result.failure(error) } } let result: Result<WeatherReport, Error> = loadFile(resource: "weather", type: "json") .map(cleanData) .map(WeatherReport.init) return result
  47. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) .map(WeatherReport.init) return result
  48. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) .map(WeatherReport.init) return result
  49. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .map { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } }
  50. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .map { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } }
  51. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .map { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } }
  52. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .map { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } }
  53. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .map { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } }
  54. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .map { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } }
  55. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .flatMap { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } }
  56. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .flatMap { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } }
  57. func loadWeatherReport() -> Result<WeatherReport, WeatherError> { // … } func

    loadWeatherReport() -> Result<WeatherReport, Error> { // … }
  58. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .flatMap { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } }
  59. func loadWeatherReport() -> Result<WeatherReport, Error> { } let result: Result<WeatherReport,

    Error> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .flatMap { (data: Data) -> Result<WeatherReport, WeatherError> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } }
  60. func loadWeatherReport() -> Result<WeatherReport, WeatherError> { } let result: Result<WeatherReport,

    WeatherError> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .flatMap { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } } .mapError { _error in WeatherError.failedToLoad }
  61. func loadWeatherReport() -> Result<WeatherReport, WeatherError> { } let result: Result<WeatherReport,

    WeatherError> = loadFile(resource: "weather", type: "json") .map(cleanData) return result .flatMap { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } } .mapError { _error in WeatherError.failedToLoad }
  62. func loadWeatherReport() -> Result<WeatherReport, WeatherError> { } let result: Result<WeatherReport,

    WeatherError> = loadFile(resource: "weather", type: “json”) .map(cleanData) return result .flatMap { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } } .mapError { _error in WeatherError.failedToLoad } .flatMapError { _error -> Result<Data, Error> in loadFile(resource: "weather_backup", type: "json") }
  63. func loadWeatherReport() -> Result<WeatherReport, WeatherError> { } let result: Result<WeatherReport,

    WeatherError> = loadFile(resource: "weather", type: “json”) .map(cleanData) return result .flatMap { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } } .mapError { _error in WeatherError.failedToLoad } .flatMapError { _error -> Result<Data, Error> in loadFile(resource: "weather_backup", type: "json") }
  64. func loadWeatherReport() -> Result<WeatherReport, WeatherError> { } let result: Result<WeatherReport,

    WeatherError> = loadFile(resource: "weather", type: “json”) .map(cleanData) return result .flatMap { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } } .mapError { _error in WeatherError.failedToLoad } .flatMapError { _error -> Result<Data, Error> in loadFile(resource: "weather_backup", type: "json") }
  65. func loadWeatherReport() -> Result<WeatherReport, WeatherError> { } let result: Result<WeatherReport,

    WeatherError> = loadFile(resource: "weather", type: “json”) .map(cleanData) return result .flatMap { (data: Data) -> Result<WeatherReport, Error> in if let report = WeatherReport(data: data) { return Result.success(report) } else { return Result.failure(WeatherReport.failedToLoad) } } .mapError { _error in WeatherError.failedToLoad } .flatMapError { _error -> Result<Data, Error> in loadFile(resource: "weather_backup", type: "json") }