Restructuring Condition Clauses
01 何が問題だったのか
Swift の guard / if / while の条件部分(condition clause)には、Boolean 式・オプショナル束縛(let)・パターンマッチング(case)・可用性チェック(#available)といった異なる種類の条件を並べて書けます。しかし Swift 2 までの文法には、カンマが「節と節の区切り」と「同じ節の中で複数の項目を並べる区切り」の両方 に使われているというあいまいさがありました。
たとえば Swift 2 では、if let の後ろに Boolean 条件をそのまま続けることができず、where 節として書く必要がありました。
guard
x == 0,
let y = optional where z == 2
else { ...
この z == 2 は意味的にはオプショナル束縛と対等な独立した Boolean 条件なのに、文法の都合で let y = optional の where 節という「従属物」のように書かされていました。そもそも where 節は for ループや switch のパターンマッチングで使うときには自然ですが、ここでは特に役立っておらず、「let の後ろに Boolean を書きたければ where を使う」というルールを知らないと Swift の条件式を十分に活用できない、という状態でした。
さらに、カンマの二重利用は純粋な文法上のあいまいさも生んでいました。次のコードを考えます。
if case let x = a, let y = b {
このコードは、
- 「
case節の中で、let x = aとlet y = bという2つのパターン束縛を並べている」 - 「
if case let x = aの後ろに、if let y = bという独立したオプショナル束縛が続いている」
のどちらとも読めてしまいます。同様に、次のような複数束縛の書き方もカンマが節区切りなのか項目区切りなのかが曖昧でした。
guard let x = opt1, y = opt2, z = opt3, booleanAssertion else { }
文法規則そのものも、「Boolean 式は先頭に書くか、それ以外の条件と結合するために where を使うか」といった場合分けが複雑で、可用性チェックだけ特別扱いされるなど、例外の多いものになっていました。この根本原因が、カンマを節区切りと項目区切りの両方に使っている ことだったのです。
02 どのように解決されるのか
条件部分の文法を、カンマはあくまで節と節の区切りとしてだけ使い、ひとつの節にはひとつの条件しか書かない という形に整理しました。これに合わせて、条件部分の where 節は廃止され、Boolean 条件は独立した節としてカンマで並べて書くようになりました。
結果として、guard / if / while の条件部分は「Boolean 式・可用性チェック・case 条件・オプショナル束縛のいずれかを、カンマで区切って並べたもの」というシンプルな構造になりました。
書き方の変化
冒頭の例は、次のように書き換えられます。where を使わず、Boolean 条件 z == 2 が let y = optional と対等な独立した節として並びます。
guard
x == 0,
let y = optional,
z == 2
else { ...
// 1行にまとめてもよい
guard x == 0, let y = optional, z == 2 else { ...
同じ guard の中で、オプショナル束縛とその結果を使った Boolean 条件を自然に組み合わせられるようになります。
guard
let x = foo(),
let y = bar(),
let z = bas(),
x == y || y == z else {
}
let / case は1条件につき1項目
カンマの役割を節区切りに一本化した代償として、ひとつの let 条件や case 条件で複数の束縛をまとめて書くことはできなくなりました。それぞれの束縛には、毎回 let や case を付け直す必要があります。
Swift 2 では次のように書けました。
// Swift 2: 1つの let の後ろにカンマ区切りで複数束縛できた
guard let x = opt1, y = opt2, z = opt3 else { }
これは Swift 3 以降では、束縛ごとに let を書く形に統一されます。
// Swift 3 以降: それぞれの束縛に let を付ける
guard let x = opt1, let y = opt2, let z = opt3 else { }
if case を複数並べる場合も同様で、節ごとに case キーワードを書きます。これにより、冒頭で挙げたあいまいなコードは次のように一意に書き分けられるようになりました。
// if case の後ろに if let が続く、と明確に読める
if case let x = a, let y = b {
// if case を2つ並べたい場合は、case を2回書く
if case let x = a, case let y = b {
switch の case ラベルには影響しない
この変更が対象にしているのは、guard / if / while の条件部分(condition clause)に現れる case 条件です。switch 文の case ラベルに複数のパターンをカンマで並べる書き方(case .a, .b:)は別の文法であり、この提案による影響を受けません。
まとめ
- 条件部分のカンマは、節と節の区切り専用 になりました。
letやcaseの条件は、1節につき1束縛 です。複数束縛したいときはlet/caseを繰り返します。- 条件部分の
where節は廃止され、Boolean 条件は独立した節として書きます。 - 既存コードは、
whereをカンマに置き換えたり、各束縛にletを補ったりする機械的な移行で対応できます(コンパイラの fix-it が用意されました)。
いずれも Swift 3 で実装され、現在の Swift の条件式の書き方そのものになっています。