Swift 5 のResultに備える

Swift の機能提案は [Swift Evolution](https://github.com/apple/swift-evolution) で行われるのですが、 Swift の標準ライブラリに `Result` 型を追加するプロポーザル [SE-0235](https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md) が承認されました。その結果、 Swift 5 で `Result` が標準ライブラリに追加されます。

本投稿では

- `Result` とは何か
- いつ `Result` を使うべきか
- `Result` をどのように扱うべきか
- 今のうちに `Result` に備える方法

を説明します。

## `Result` とは何か

Swift にはサードパーティ製の [antitypical/Result](https://github.com/antitypical/Result) という人気ライブラリがあり、 `Result` に馴染みのある人も多いと思います。本節では、 `Result` に馴染みのない人のために `Result` について簡単に説明します。

`Result` はエラーハンドリングに用いられる型で、次のように宣言されます。

```swift
public enum Result<Success, Failure: Error> {
    case success(Success)
    case failure(Failure)
}
```

`Result` を用いると、たとえば、ファイルの内容を `Data` インスタンスとして読み込む関数 `readFile` のシグネチャは次のように書けます。

```swift
func readFile(at path: String) -> Result<Data, IOError>
```

ただし、入出力に伴うエラーは次のような `IOError` というエラー型で表されるものとしています。

```swift
enum IOError: Error {
    case fileNotFound(path: String)   // ファイルが存在しないとき
    case permissionDenied(Permission) // パーミッションを持たないとき
    ...
}
```

このとき、 `readFile` 利用時のエラーハンドリングは次のようになります。

```swift
switch readFile(at: path) {
case .success(let data):
    ... // `data` を使うオペレーション
case .failure(.fileNotFound(path: let path)):
    ... // ファイルが存在しないときのエラーハンドリング
case .failure(.permissionDenied(let permission)):
    ... // パーミッションを持たないときのエラーハンドリング
case .failure(let error):
    ... // その他の場合のエラーハンドリング
}
```

### `throws` との関係

`Result` と `throws` でできることはよく似ています。 `throws` を使うと `readFile` の例は次のように書けます。

```swift
func readFile(at path: String) throws -> Data
```

エラーハンドリングの例は次の通りです。

```swift
do {
    let data = try readFile(at: path)
    ... // `data` を使うオペレーション
} catch IOError.fileNotFound(path: let path) {
    ... // ファイルが存在しないときのエラーハンドリング
} catch IOError.permissionDenied(let permission) {
    ... // パーミッションを持たないときのエラーハンドリング
} catch let error {
    ... // その他の場合のエラーハンドリング
}
```

構文の違いや `throws` ではエラー型を指定できないという違いはありますが、できることはほぼ同じです。

## いつ `Result` を使うべきか

`Result` と `throws` は似た機能なのでどのように使い分ければ良いのかという疑問が生まれると思います。本節では `Result` と `throws` の使い分けについて説明します。

### Manual Propagation と Automatic Propagation

Swift のエラーハンドリングを語るときに欠かすことのできない [Error Handling Rationale and Proposal](https://github.com/apple/swift/blob/master/docs/ErrorHandlingRationale.rst) というドキュメントがあります。これは Swift 2.0 で `throws/try` が追加されるに当たって [Swift Core Team](https://swift.org/community/#core-team) のメンバーによって書かれたドキュメントで、 Swift におけるエラーハンドリングがどのように設計されたかを知ることができる貴重な資料です。

その中で Manual Propagation と Automatic Propagation という概念が示されています。エラー値を通常の値と同じように扱い（戻り値として受け取り、変数や定数に格納するなど）、通常の制御構文（ `if`, `guard`, `switch` 文など）を用いて取り扱う場合、それは Manual Propagation です。一方、 `do/try/catch` のように専用の構文を用いてエラー発生箇所からハンドリング箇所へ暗黙的にジャンプするような場合は Automatic Propagation です。

現状（ Swift 4.2 ）の Swift の標準ライブラリや Foundation でもこれらは使い分けられています。

たとえば、文字列を整数に変換するコードは次のように書きます。

```swift
let numberOrNil: Int? = Int(string)
guard let number = numberOrNil else {
    ... // エラーハンドリング
}
... // `number` を使うオペレーション
```

`Int(string)` の戻り値の型は `Int?` で、 `nil` によってエラーを表します。この場合は `Optional` や `guard let` （ Optional Binding ）という通常の言語機能を使ってエラーハンドリングを行っており、 Manual Propagation です。

一方、 `Data` をファイルに書き込む処理は次のようになります。

```swift
do {
    try data.write(to: URL(fileURLWithPath: path))
    ... // 何らかのオペレーション
} catch let error {
    ... // エラーハンドリング
}
```

この場合、 `data.write` の呼び出し箇所から `catch` までジャンプすることになり Automatic Propagation です。

`Result` か `throws` かという視点で考えると、 `Result` は Manual Propagation 、 `throws` は Automatic Propagation ということになります。

Automatic Propagation は多くの場合 Manual Propagation よりもコードを簡潔に書くことができます。たとえば、次のコードは Automatic Propagation で書かれたものですが、これと同じオペレーションを Manual Propagation で書こうとすると大変です。

```swift
let db: Database = ...
do {
    let group: Group = try db.get(Group.self, withKey: groupID)!
    let owner: User = try db.get(User.self, withKey: group.ownerID)!
    let ownerFriends: User = try db.get([User.self], withKeys: owner.friendIDs)
    ... // `ownerFriends` を使うオペレーション
} catch DatabaseError.connectionFailure {
    ... // 接続に失敗した場合のエラーハンドリング
} catch DatabaseError.transactionError {
    ... // トランザクション関連のエラーハンドリング
} catch let error {
    ... // その他の場合のエラーハンドリング
}
```

これは当たり前の話で、 Manual Propagation は通常の言語機能を使うので、もし Manual Propagation で十分なのであれば言語が Automatic Propagation のための構文を用意する必要はありません。わざわざ Automatic Propagation ができるようになっているのは、 Manual Propagation だけでは何らかの不便があり、それを解決したいという意図があったからです。

しかし、それならすでに Automatic Propagation のための `throws` があるのに、今さら何のために Manual Propagation のための `Result` が追加されようとしているのでしょうか。それは、 Manual Propagation にはできて Automatic Propagation にはできないことがあるからです。

Automatic Propagation はエラーが発生したら即時ハンドリングすることが強制されます。一方、 `Result` や `Optional` はエラーハンドリングしないまま引数や戻り値として取り回したり、プロパティに保持しておいたりすることができます。実際に、そのような Manual Propagation でしか扱えないケースに対応するために、サードパーティ製の `Result` ライブラリが使われています。

しかし、ライブラリ A がある `Result` ライブラリを、ライブラリ B が別の `Result` ライブラリを、ライブラリ C が依存性を避けるために独自の `Result` 型を使っているというようなケースが頻発し、 `Result` 同士の互換性が問題となっていました。 `Result` を標準ライブラリに追加する主な目的は、標準の `Result` 型を導入することでそのような互換性の問題を解決することです。

### Manual Propagation でしか解決できないケース

Manual Propagation でしかできない例をいくつか紹介します。

#### 非同期処理

URL を指定してファイルをダウンロードする関数 `download` を考えてみましょう。当然、この関数はネットワークエラーを起こす可能性があります。単純に考えると次のようなシグネチャになりそうです。

```swift
func download(from url: URL) throws -> Data
```

しかし、これは望ましくありません。上記の `download` 関数は同期処理になってしまっています。もし UI スレッドで上記のような関数を呼ぶとダウンロードが完了するまで画面が固まってしまうでしょう。非同期処理とするために、コールバックのクロージャを指定できるようにしたいところです。

次のようにコールバックで `Data` を受け取るようにするとどうでしょうか？

```swift
func download(from url: URL, _ completion: (Data) -> Void) throws
```

なんとなくシグネチャの上ではうまくいきそうに見えるかもしれませんが、これではうまくいきません。上記の `download` を使うコードは次のようになります。

```swift
do {
    try download(from: url) { data in
        ... // `data` を使うオペレーション [A]
    }
} catch let error {
    ... // エラーハンドリング [B]
}
```

上記のコードの [B] は `download` を呼び出した直後に実行されることになります。しかし、その時点ではダウンロードは終わっていません。将来起こるかもしれないエラーのハンドリングを先に行うことはできません。非同期処理では、エラーハンドリングもオペレーション終了後に実行される [A] でやらなければなりません。

しかし、 [A] でエラーハンドリングさせるような `download` 関数を `throws` でうまく書くことはできません。無理やり次のように書くことができないわけではないですが非常にわかりづらいです。

```swift
func download(from url: URL, _ completion: (() throws -> Data) -> Void)
```

そのため、次のような設計になっていることが多いです。

```swift
func download(from url: URL, _ completion: (Data?, Error?) -> Void)
```

ダウンロードに成功した場合は `(Data?, Error?)` として `(data, nil)` を、失敗した場合は `(nil, error)` を受け取ります。

```swift
download(from: url) { data, error in
    if let data = data {
        ... // `data` を使うオペレーション
    } else {
        let error = error! // ここで ! が必要
        ... // エラーハンドリング
    }
}
```

残念ながらこれは型として厳密ではありません。 `(Data?, Error?)` は `(data, nil)`, `(nil, error)` の他に `(data, error)`, `(nil, nil)` も許容します。それらが仕様として起こり得なくても型として表現できていないので、上記のコードのように `!` が必要になってしまっています。

こんなときに `Result` を使うと型を厳密に書くことができます。

```swift
func download(from url: URL, _ completion: (Result<Data, DownloadError>) -> Void)
```

```swift
download(from: url) { result in
    switch result {
    case .value(let data):
        ... // `data` を使うオペレーション
    case .failure(let error):
        ... // エラーハンドリング
    }
}
```

##### `async/await`

非同期処理は `Result` のユースケースのかなりの割合を占めると予想されますが、[現在提案されている `async/await`](https://gist.github.com/lattner/429b9070918248274f25b714dcfc7619) が導入されるとほとんどの非同期処理で `Result` は不要になります。

ここでは `async/await` についての詳しい解説は行いませんので、興味があれば以前に書いた次の投稿を御覧下さい。

- ["Proposalには載っていないSwift 5のasync/awaitが素晴らしいと思う理論的背景"](https://qiita.com/koher/items/29357b5e00aec1962601)
  - タイトルに「 Swift 5 の」と入っていますが、 Swift 5.0 で導入されないことは決定で、おそらく現状では Swift 6 以降になると思われます。

`async` を使えば、前述の `download` 関数は `throws` と組み合わせて次のように書けるようになります。

```swift
func download(from url: URL) async throws -> Data
```

`download` を使う側のコードも `await` と `try` を使って簡潔に書けます。

```swift
do {
    let data = try await download(from: url)
    ... // `data` を使うオペレーション
} catch let error {
    ... // エラーハンドリング
}
```

非常にシンプルですね。もし `async/await` が導入されれば、このようなケースであえて `Result` を使う必要はありません。

#### 並行した非同期処理

しかし、 `async/await` で書くことのできない非同期処理もあります。 `async/await` では処理を順番に行うので、次のようなコードでは A をダウンロードしてから B をダウンロードすることになります。

```swift
let dataA = try await download(from: urlA)
let dataB = try await download(from: urlB)
... // `dataA` と `dataB` を使うオペレーション
```

A と B を並行にダウンロードしたい場合は `async/await` だけでは書くことができません。そのため、[プロポーザルで言及されている `Future` のような型](https://gist.github.com/lattner/429b9070918248274f25b714dcfc7619#entering-and-leaving-async-code)が必要になります。この `Future` は `Result` の提案以前に書かれたものなので内部に独自の `Result` 型を持っています。また、 `Future` が `Result` の機能も併せ持ったものになっています。もし `Future` から `Result` の機能を分離したとすると、 A と B を並行でダウンロードするオペレーションは次のように書けます[^1]。

```swift
let futureA: Future<Result<Data, DownloadError>> = Future { try await download(from: urlA) }
let futureB: Future<Result<Data, DownloadError>> = Future { try await download(from: urlB) }
let dataA: Data = try await futureA.get()
let dataB: Data = try await futureB.get()
... // `dataA` と `dataB` を使うオペレーション
```

このように、 `async/await` が導入されたからといってすべての非同期処理で `Result` が不要になるわけではありません。

#### `Observable` などの型と組み合わせる場合

[RxSwift](https://github.com/ReactiveX/RxSwift) の `Observable` など、ジェネリックな型の型パラメータと組み合わせて使いたい場合には Manual Propagation が必要となります。 `Observable<Result<Foo, BarError>>` のような型を `throws` で書こうとすると、先のコールバックのように `Observable<() throws -> Foo>` のように複雑になってしまうので、このような場合は `Result` を用いるのが適当です。

### いつ `Result` を使うことが想定されているか

`Result` をどのような場合に使うことが想定されているのでしょうか。僕が注目したのは Swift Core Team のメンバーである John McCall の次のコメントです。 John McCall は "Error Handling Rationale and Proposal" の著者でもあります。

> I want to clarify that I favor adding `Result` *only* to address situations where manual propagation is necessary.
> 参考訳: 私は Manual Propagation が必要となるような状況に対処するため *だけ* に `Result` を追加することを支持するとはっきりさせたい。
>
> I will continue to recommend against using `Result` as an alternative mechanism for ordinary propagation of errors
> 参考訳: 通常のエラーの propagation の代わりとして `Result` を使用しないように引き続き推奨していく
> ( John McCall: https://forums.swift.org/t/se-0235-add-result-to-the-standard-library/17752/129 )

このように、 `Result` は `throws` で書くことが困難で、 Manual Propagation が必要になったときに **だけ** 使うのが良いでしょう。そして、 `async/await` が導入されると多くの非同期処理では `Result` が不要となるので、 `Result` を利用すべきケースは限定的であると言えます。 `Result` の使用は必要最小限に留め、本当に `Result` がないと（ `throws` では）書けないようなコードを書くときにだけ利用することをオススメします。

ただ、他の Core Team メンバーのコメントなども見ているといつ `Result` を使うべきか、必ずしも Core Team の中でもコンセンサスが得られているわけではない印象を受けました。そのため、レビュー時に[いつ `Result` を使うべきと意図されているのかを示してほしいというコメントを書き込んでみました](https://forums.swift.org/t/revised-se-0235-add-result-to-the-standard-library/18371/26)。残念ながら直接の返信は得られなかったものの、全体へのアナウンスの中に次のように書かれていたので、 `Result` が実際に追加されるときにはいつ使うべきかもより明確に示されるものと思われます。おそらく、僕は John McCall のコメントにあるような、 Manual Propagation が必要なケースでのみ `Result` を利用するということで落ち着くのではないかと考えています。

> The Core Team acknowledges the call from several reviewers to make a special effort to communicate how we think `Result` ought to be used in Swift. We absolutely agree that is an important part of adding `Result` to the language, and we intend to provide this guidance as part of the release.
> 参考訳: Swift において `Result` がどのように使用されるべきと Core Team が考えているのかコミュニケートしようと特別な努力を払った何人かのレビュワーからの要望を Core Team は認知している。私達は、それは `Result` の言語への追加の重要な一部であると完全に同意する。そして、私達はそのようなガイダンスを `Result` のリリースの一部とするつもりだ。
> ( John McCall: https://forums.swift.org/t/accepted-with-modifications-se-0235-add-result-to-the-standard-library/18603 )

### エラー型の指定

`Result` と `throws` は似た仕組みを提供しますが、 Manual Propagation か Automatic Propagation かということ以外に大きく異なるのがエラー型を指定できるかどうかです。 Swift 4.2 時点では、 Java のように `throws` の後ろにエラー型を指定することはできません。

```swift
// これはできない
func readFile(at path: String) throws IOError -> Data
```

現状で（サードパーティ製の） `Result` が利用されている目的の一つにエラー型を指定したいということがあります。 `throws` でエラー型を指定できないのは意図的な設計によるものです。僕の解釈では、それは次のような理由によります。

エラーハンドリングのほとんどはオペレーションに成功したか失敗したかがわかればよく、エラーの型は重要ではありません（特にアプリケーションの実装においてはその傾向が強いと思います）。エラーの種類によって分岐したいようなケースでも、原因ごとの網羅的な分岐が必要になることは稀です。大抵は一つ二つの原因とその他というような分け方をすることになります。

たとえば、多様な原因を持つ I/O エラーを個別に分岐することはほぼないと思います。

```swift
do {
    let data = try readFile(at: path)
    ... // `data` を使うオペレーション
} catch IOError.fileNotFound(path: let path) {
    ... // ファイルが存在しないときのエラーハンドリング
} catch IOError.permissionDenied(let permission) {
    ... // パーミッションを持たないときのエラーハンドリング
} catch let error { // ←ここを個別に分岐して対応することは少ない
    ... // その他の場合のエラーハンドリング
}
```

他にも、ネットワークエラーを HTTP のすべてのステータスコードに個別に対応するコードを書いたことのある人はまずいないでしょう。 404 などのいくつかのケースに対応して、後はその他としてまとめてしまうことがほとんどのはずです。

このように、エラー型に対して網羅的かつ個別に分岐が必要となるケースは稀です。それに対して、エラー型を上手く設計するのは困難です。後から `case` の追加がしたくなったり、エラー型の種類を追加したくなったりすることはよくあることです。原因を整理してエラー型の実装を作り変えたくなることも多いでしょう。

もし、 `throws` にエラー型を指定することができると、おそらく多くのプログラマはそれを丁寧に設計しようとするはずです。しかし、それによって得られるリターンとそのために費やしたコストを天秤にかけると、コストの方が大きいケースが多いのではないでしょうか。そのような現実を踏まえて、現状では `throws` にエラー型を指定できないようになっているのではないかと思います。

しかし、エラー型を指定したいケースが存在するのも確かです。特に、ライブラリなどではエラー型が明確な方が良いことも比較的多いでしょう。 JSON のデコードのようなオペレーションでは、考えうるエラーの原因が数個程度に限定でき、個別に分岐したいケースも現実的に起こり得ます。

そのような場合にエラー型を指定するために `Result` を使うべきかと言うと、僕はオススメできません。エラー型を指定したいというのは前述の Manual Propagation が必要な場合には該当しません。

また、 Typed Throws （ `throws` の後ろにエラー型を指定できる構文）も Swift Evolution で議論され続けています。将来的にそのような構文が追加される可能性もあり、前述の JSON のデコードのようなケースは Typed Throws で対応すべきものだと思います。現状ではエラー型を指定できない状態の `throws` で実装しておき、 Typed Throws が追加されたときにエラー型を指定するのが良いでしょう。

#### `Result<T, Error>`

逆に、 `Result` に不必要にエラー型を指定するのは避けるのが良いでしょう。特に、非同期処理で `Result` を使うようなケースでは、 `async/await` さえあれば本来エラー型の指定されない `throws` で十分なことが多いはずです。そういう場合には `Result<Data, Error>` のように型パラメータ `Failure` に汎用的な `Error` を指定し、汎用的なエラー型として扱うのが良いと思われます。

```swift
func download(from url: URL, _ completion: (Result<Data, Error>) -> Void)
```

```swift
download(from: url) { result in
    switch result {
    case .value(let data):
        ... // `data` を使うオペレーション
    case .failure(let error):
        ... // エラーハンドリング
    }
}
```

Swift ではプロトコル型はそのプロトコル自身に適合しないという制約があったため、 `Result<Data, Error>` のような型を作ることができませんでした。型パラメータ `Failure` には `Failure: Error` という制約がありますが、 `Error` は `Error` 自身に適合しないので `Failure` の制約を満たせませんでした。この制約は言語の実装上の都合によるものですが、 `Result` の導入に伴って問題が生じるので、応急処置的に `Error` に関してのみ[自分自身のプロトコルに適合する仕様（ self-conformance ）](https://github.com/apple/swift-evolution/blob/master/proposals/0235-add-result.md#adding-swifterror-self-conformance)が追加されます。積極的にこれを活用しましょう。

## `Result` をどのように扱うべきか

いつ `Result` を使うべきかがわかったとして、実際に `Result` を返すような API があった場合にどのように `Result` を扱えば良いでしょうか。ここでは簡単に僕のオススメを書いておきます。

`Result` を受け取ったらできるだけすぐにハンドリングすると良いでしょう。特に、非同期処理の結果として受け取った場合に、それ以上 `Result` のまま値を取り回す必要はないはずです。成功か失敗かが知りたいだけなら、 `get` と `do/try/catch` で十分でしょう。

```swift
download(from: url) { result in
    do {
        let data: Data = try result.get()
        ... // `data` を使うオペレーション
    } catch let error {
        ... // エラーハンドリング
    }
}
```

もし、エラーの原因別に分岐してハンドリングが必要なケースでは、網羅的な分岐が書きやすい `switch/case` を使うのが良いでしょう。

`map`, `flatMap`, `mapError`, `flatMapError` の利用は、個人的には [`Optional` 同様に最小限に留めておくのが良い](https://qiita.com/koher/items/8b6156c8263b9b23c43c#mapflatmap%E3%81%AF%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3%E3%81%8B%E9%96%A2%E6%95%B0%E3%81%AB%E6%B8%A1%E3%81%99%E3%81%A8%E3%81%8D%E3%81%AB%E4%BD%BF%E3%81%86)と思います。それらが必要なケースが存在するのは確かですが、 `switch/case` や `do/try/catch` と適切に使い分けないと可読性を損なうでしょう。

## 今のうちに `Result` に備える方法

`Result` が導入されると、既存のコードベースの中でサードパーティ製 `Result` を使っている箇所や、コールバックで値とエラーを受け取っているケース（前述の `func download(from url: URL, _ completion: (Data?, Error?) -> Void)` など）を標準ライブラリの `Result` を使って書き換えることになると予想されます。できるだけ移行の負荷を小さくするために、今のうちから標準ライブラリの `Result` に備えておきたいところです。

そのために、 SE-0235 で提案されている `Result` 互換の `Result` 型ライブラリ *SwiftResult* を作りました。

- *SwiftResult*: https://github.com/koher/SwiftResult

今のうちに *SwiftResult* の `Result` を使ったコードに変更しておけば、 Swift 5 で `Result` が導入されたときに `import SwiftResult` を消すだけで移行が完了するはずです。是非ご活用下さい。

[^1]: `Future<Result<T, E: Error>>` に対して `Future` と `Result` を同時にアンラップする `extension` メソッド `get` が実装されているものとします。