[Swift] Resultで例外をいい感じに処理する


## 注意

***これはネタエントリーです***

Swiftの公式見解は「例外は例外としてちゃんと処理しろ。Resultはそういうものじゃねぇ」です。
ご理解の上お読みいただけますようお願いいたします。

##なにこれ？

例外処理を do - try - catchを使わずResultで処理しちゃおうという奴です。


## 準備

[こちらのエントリ](https://qiita.com/masakihori/items/06cf8ef7a0ebf8eb2a06)にある ifSuccess / ifFailure を利用しますので確認しておいてください。

## じゃあやってみようか

特に難しいことをしようというのではありませんのでコードをドーンと出しちゃいます。

以下のようなコードをResultで書き換えちゃいます。

```swift
struct HogeError: Error {}
func hoge(_ s: String) throws -> Int {
  guard let i = Int(s) else { throw HogeError() }
  return i
}

do {
  print(try hoge("123"))
}
catch {
  print(error)
}

do {
  print(try hoge("abc"))
}
catch {
  print(error)
}
```
とくに解説はいりませんね。

では書き換えます。

```swift
struct HogeError: Error {}
func hoge(_ s: String) throws -> Int {
  guard let i = Int(s) else { throw HogeError() }
  return i
}

Result { try hoge("123") }
  .ifSuccess { print($0) }
  .ifFailure { print($0) }
  
Result { try hoge("abc") }
  .ifSuccess { print($0) }
  .ifFailure { print($0) }
```
僕にはかなり読みやすいのですが、皆さんはどうでしょう？

## エラーを複雑にしてみる　１
例外処理では複雑な例外に対処する必要が多分に発生します。
まずはenumな例外を見てみましょう。

```swift
enum HogeError: Error {
 case notNumber
 case minus
 case lessThan10(Int)
}
func hoge(_ s: String) throws -> Int {
  switch Int(s) {
  case nil: throw HogeError.notNumber
  case (...0)?: throw HogeError.minus
  case let n? where n < 10 : throw  HogeError.lessThan10(n)
  case let n?: return n
  }
}
```
do-try-catchでは以下のようになります。

```swift
do {
  print(try hoge("abc"))
}
catch HogeError.notNumber {
  print("not number")
}
catch HogeError.minus {
  print("minus")
}
catch let HogeError.lessThan10(n) {
  print(n, "is less than 10.")
}
catch {
  print("we caught unknown error.", error)
}
```
ごく普通です。

ではこれをResultで処理してみましょう。
そのために新たなstructを導入しResultのextensionに手を加えます。
[長いので読み飛ばしましょう](#ここにjump)

```swift
struct ResultErrorHandler<Failure: Error> {
  let failure: Failure?
  let handled: Bool

  func ifFailure<F: Error>(as type: F.Type, _ f: (F) -> Void) -> Self {
    if let error = failure as? F {
      f(error)

      return .init(failure: failure, handled: true)
    }

    return self
  }

  func ifFailure(_ f: (Failure) -> Void) {
    if handled { return }
    guard let failure = failure else { return }

    f(failure)
  }
}

extension Result {
  @discardableResult
  func ifSuccess(_ f: (Success) -> Void)-> Self {
    if case let .success(v) = self { f(v) }
    return self
  }

  func ifFailure(_ f: (Failure) -> Void) {
    if case let .failure(error) = self { f(error) }
  }

  func ifFailure<F: Error>(as type: F.Type, _ f: (F) -> Void) -> ResultErrorHandler<Failure> {
    guard case let .failure(error) = self else {
      return .init(failure: nil, handled: false)
    }
    if let e = error as? F {
      f(e)
      return .init(failure: error, handled: true)
    }

    return .init(failure: error, handled: false)
  }
}
```

#### ここにJump
これで準備が出来ましたのでResultを使った例外処理に書き換えてみましょう。

```swift
Result<Void, Error> { print(try hoge("abc")) }
  .ifFailure(as: HogeError.self) { error in
    switch error {
    case .notNumber: print("not number")
    case .minus: print("minus")
    case let .lessThan10(n): print(n, "is less than 10.")
    }
  }
  .ifFailure { error in
    print("we caught unknown error.", error)
  }
```

読み飛ばしていただいたextensionで実装された`ifFailure(_:)`メソッドに渡された関数/クロージャは、その前に実行された`ifFailure(as:)`メソッドがすでにエラーハンドリングを終えている場合は実行されません。
つまりこの場合ですとFailureが`HogeError`しか起こりえませんので、`ifFailure(_:)`メソッドに渡した関数/クロージャが実行されることはありません。

残念ながらenumの個別のcaseごとに処理が分けられません。
この点はdo-try-catchの方がよいかもしれません。
ただし、こちらの場合はswitchによるcaseの網羅性チェックが入りますのでどちらが良いかは微妙です。
もちろんその方法もdo-try-catchで記述可能です。

## エラーを複雑にしてみる　２
複数のtypeのエラーが投げられる場合。

例外が投げられる可能性のあるAPI呼び出しを含むメソッドが独自の例外を投げる可能性もある。という場合などです。
メソッド内でAPI側の例外を処理しろって話ですが、今回はそういうのはなしで。

```swift
enum HogeError: Error {
 case notNumber
 case minus
 case lessThan10(Int)
}

struct FugaError: Error {}

func getString() throws -> String {
  throw FugaError()
}

func hoge() throws -> Int {
  switch Int(try getString()) {
  case nil: throw HogeError.notNumber
  case (...0)?: throw HogeError.minus
  case let n? where n < 10 : throw  HogeError.lessThan10(n)
  case let n?: return n
  }
}
```
HogeErrorに加えてFugaErrorを定義しました。
また文字列を取得するthrowableな`getString()`関数を定義し、関数`hoge()`はそれを利用して文字列を取得するように変更しました。

ではこれをdo - try - catchを使って書いてみます。

```swift
do {
  print(try hoge())
}
catch HogeError.notNumber {
  print("not number")
}
catch HogeError.minus {
  print("minus")
}
catch let HogeError.lessThan10(n) {
  print(n, "is less than 10.")
}
catch is FugaError {
  print("Can not get string.")
}
catch {
  print("we caught unknown error.", error)
}
```
`catch is FugaError`が追加されただけで他は変わっていません。

ではこれをResultを使って書き換えます。

```swift
Result<Void, Error> { print(try hoge()) }
  .ifFailure(as: HogeError.self) { error in
    switch error {
    case .notNumber: print("not number")
    case .minus: print("minus")
    case let .lessThan10(n): print(n, "is less than 10.")
    }
  }
  .ifFailure(as: FugaError.self) {
    print("Can not get string.")
  }
  .ifFailure { error in
    print("we caught unknown error.", error)
  }
```

こちらも`.ifFailure(as: FugaError.self)`が追加されただけで他は変わっていません。


## エラーを複雑にしてみる　３
Errorに継承関係がある場合
ちょっと特殊だとは思いますがErrorに継承関連がある場合を考えます。

```swift
class E0: Error {}
class E1: E0 {}

func fuga(_ e1Flag: Bool) throws -> String {
  if e1Flag {
    throw E1()
  }
  
  throw E0()
}

```

これをdo - try - catchで書いてみます。

```swift
do {
  print(try fuga(true))
}
catch let error as E0 {
  print("E0", error)
}
catch let error as E1 {
  print("E1", error)
}
catch {
  print("Other", error)
}
```
特に問題はありませんね。
と思いますがこれは問題ありです。
出力結果はこうなります。

```sh
E0 E1
```

投げられた例外はE1です。しかしcatchしたのはE0としてです。
do - try - catchではswitch-caseと同様に記述された順番に検査され最初に合致したcatchのみが実行されそれ以降は無視されます。
E1はE0でもあるためこのような結果を得ることになります。

ただしい書き方はこうなります。

```swift
do {
  print(try fuga(true))
}
catch let error as E1 {
  print("E1", error)
}
catch let error as E0 {
  print("E0", error)
}
catch {
  print("Other", error)
}
```

E1（継承先、子孫）の場合をE0（継承元、親、先祖）の前に記述します。


ではこれをResultで書き換えます。

```swift
Result<Void, Error> { print(try fuga(true)) }
  .ifFailure(as: E0.self) { error in
    print("E1", error)
  }
  .ifFailure(as: E1.self) { error in
    print("E1", error)
  }
  .ifFailure { error in
    print("Other", error)
  }
```
出力結果

```sh
E0 E1
```

先に提示したextensionで実装された`ifFailure`メソッドのシリーズはメソッドチェーンによる記述となるため記述した順に実行されるのは自明です。
また、一度処理を実行した場合はそれ以降の処理を行わない設計となっていますので、出力結果はdo - try - catchと同様になります。
こちらも以下のように書き換えることで対応します。

```swift
Result<Void, Error> { print(try fuga(true)) }
  .ifFailure(as: E1.self) { error in
    print("E1", error)
  }
  .ifFailure(as: E0.self) { error in
    print("E1", error)
  }
  .ifFailure { error in
    print("Other", error)
  }
```

これで意図した結果が得られます。

## まとめ

いかがでしたでしょうか？

適切なextensionを導入することでdo - try - catchを使用するのと遜色なくResultを例外処理機構として利用することができることがわかりました。


## 結論

だからといって**やっちゃだめ。**

冒頭に書いたとおり、Swift開発陣は例外は例外として扱いdo - try - catchを使用することを推奨しています。
また、Resultを例外処理機構の一部として利用することを想定していません。

あくまでも**ネタエントリー**であることをご理解いただけますようお願いします。