Multi-Pattern Catch Clauses
01 何が問題だったのか
Swift の do-catch 文では、従来ひとつの catch 節に書けるパターンはひとつだけで、where 節も 1 組しか付けられませんでした。これは switch 文の case が「カンマ区切りで複数のパターンをまとめ、共通の本体を実行する」書き方をサポートしているのと対照的で、パターンマッチングの仕様に一貫性がありませんでした。
この制約のもとでは、複数のエラーケースに対して同じ処理を行いたいときに、次のような書き方ができません。
do {
try performTask()
} catch TaskError.someRecoverableError { // OK
recover()
} catch TaskError.someFailure(let msg),
TaskError.anotherFailure(let msg) { // 現状は不可
showMessage(msg)
}
そのため実際には、次のどちらかで回避する必要がありました。
catch節を複数に分け、本体の処理を重複して書く。catch節の中でswitchをネストさせ、そこでパターンをまとめる。
do {
try performTask()
} catch let error as TaskError {
switch error {
case TaskError.someRecoverableError:
recover()
case TaskError.someFailure(let msg),
TaskError.anotherFailure(let msg):
showMessage(msg)
}
}
前者は本体の重複を生み、後者は catch 節のパターンマッチング機能をわざわざ無効化して内側で switch をやり直す形になるため、catch でパターンマッチングをサポートしている意義を損なってしまいます。複数のエラーケースを同じ本体で処理したい、という素直なニーズに対して、コードが冗長になりがちだったのが問題でした。
02 どのように解決されるのか
catch 節の文法を拡張し、switch の case と同じように、ひとつの catch にカンマ区切りで複数のパターン(それぞれに任意の where 節を付けられる)を並べられるようにします。実行時に投げられたエラーが、そのうちいずれかのパターンにマッチし、かつ先行する catch 節にマッチしていなければ、その catch の本体が実行されます。
これにより、冒頭の例をそのまま書けるようになります。
do {
try performTask()
} catch TaskError.someRecoverableError {
recover()
} catch TaskError.someFailure(let msg),
TaskError.anotherFailure(let msg) { // こう書ける
showMessage(msg)
}
performTask が TaskError.someFailure("message") または TaskError.anotherFailure("message") を投げた場合、2 つ目の catch の本体が実行され、showMessage(msg) が呼ばれます。
値の束縛
switch の case と同様に、複数パターンを持つ catch 節でも値の束縛を行えます。ただしその場合、switch と同じく、同じ名前の束縛を同じ型ですべてのパターンに含める必要があります。上の例の msg がまさにそのパターンで、両方のパターンで同名・同型で束縛されているため、本体でそのまま利用できます。
文法
拡張後の catch 節の文法は次のようになります。
catch-clauses -> catch-clause catch-clauses?
catch-clause -> 'catch' catch-item-list? code-block
catch-item-list -> catch-item |
catch-item ',' catch-item-list
catch-item -> pattern where-clause? |
where-clause
trailing closure との関係
パース上の曖昧さを避けるため、catch 節のアイテムの中では、末尾に trailing closure を持つ式は書けません。これは switch の case とは異なる挙動ですが、実用上は、必要なら明示的にカッコで囲んで書けば対応できます。
今後の展望
Proposal には今回のスコープ外として、将来の検討対象がいくつか speculative に挙げられていました。いずれも実現が約束されているわけではありません。
- すべての
catch節で暗黙のエラー束縛を使えるようにする案。現在はパターンを持たないcatch節でのみ暗黙のerror: Errorが利用できますが、これを全catch節に広げると再スローなどが書きやすくなります。ただしerrorという名前をそのまま使うとソース互換性への影響が大きいため、$errorのようなコンパイラ定義の識別子を使う形が検討されています。 catch節でのfallthroughサポート。switchのcaseとさらに仕様を揃える方向ですが、外側のswitchに対するfallthroughと衝突してソース互換性を壊すこと、他言語での前例がないこと、具体的な利用シーンが確認されていないことから、現時点では導入しない方針です。catch節を条件付きコンパイルブロックで囲めるようにする案。switchのcaseに対する既存サポートと揃える方向ですが、条件付きコンパイル全体の再設計と合わせて別途検討される想定です。