この記事は [Swift Advent Calendar 2017]
(https://qiita.com/advent-calendar/2017/swift) の 3 日目の記事です。
本記事では、よく提案されるけれど採用されなかった仕様とその理由、そして、そこから読み取れる Swift の設計方針を紹介します。
主なソースはapple/swift-evolution 内の Commonly Rejected Changes とswift-evolution のメーリングリストのログです。
リジェクトされた提案
Array の範囲外にアクセスした際に nil
を返す
前提
Swift では配列(Array
)の範囲外に添え字でアクセスすると実行時エラーになります。
let array = [0, 1]
array[3] // runtime error
リジェクト理由
理由は 2 点挙げられています。
1. 範囲外アクセスはロジックエラーである
「subscript は入力に前提条件があり、それが満たされていない場合の回復処理を動的にさせるべきではない」という考えのようです。
2. 動作が遅くなる
もし、範囲外へのアクセスをした場合に nil
を返す動作にすると、範囲内であるかを毎回検査する必要がでてきます。これは許容できないほど大きなコストがかかる処理です。
余談ですが、最適化オプションによっては範囲内であるかの検査をしています。(precondition failure によるエラーが出力されているのが確認できるかと思います)
代入処理の式で値を返さない
前提
C 言語等の言語では変数を代入する式は代入される値を返しますが、Swift では Void
(空のタプル) を返します。
int v;
v = 0; // 0 を返す
var v: Bool
v = false // Void を返す
リジェクト理由
=
と ==
の書き間違えによるバグを防ぐためです。
int v = 0;
// 意図しているコード
if(v == 0) { ... } // `if(1)` と評価
// `==` と `=` を書き間違えたコード
if(v = 0) { ... } // `if(0)` と評価
var v = false
// 意図しているコード
if v == false { ... } // `if true` と評価
// `==` と `=` を書き間違えたコード
if v = false { ... } // ❗️文法エラー
;
の削除
前提
Swift は 1 行に複数の文がなければ、文末に ;
を必要としません。(改行によって文の終わりを示すことができます)
let a = 1
if cond {
...
}
しかし、1 行に複数の文を記述する場合、文の終了を明示的に示さなくてはなりません。その際に使われるのが ;
です。;
は文の終わりを表すことができます。
i.start; defer { i.end }
リジェクト理由
前述の通り、;
には表現としての意味があります。
「行末に ;
が書けてしまう」という指摘がありましたが、それは linter で管理すべき問題ということでした。
{}
の仕様をやめ Python のような字下げの記法を採用する
前提
Swift や C 言語系統の言語では、スコープやクロージャを {}
で表現しています。一方、Python では {}
ではなく、字下げを用います。
リジェクト理由
字下げも現行の仕様も、どちらも利点があります。どちらの文法を採用するかの判断基準として「比べてみて強い理由がなければ C 言語系統の言語で親しまれている方」という原則があります。
また、この中の議論で面白かったのは「Swift は”簡潔さ”を目的とせず、”表現力”を目的とする」と書かれていることでした。ときには ”簡潔さ” によって表現力が向上することもありますが、損なうこともあります。「この提案は Swift の仕様には合わない」という判断がされているようです。(Python は字下げを採用していても、他がそれに調和する設計になっています。)
?:
演算子の置き換え
特に優れた代替案が見つかっていないため、C 言語系統の言語でよく使われている文法を採用しているようです。
例えば、if ... then else ...
という案があります。
let x = cond ? 4 : 8
let x = if cond then 4 else 8
しかし、冗長で、実際の処理が見えにくいです。
また、一行で書くと読みにくい場合、以下のように改行して書きたくなります。しかしこれでは if 文にも見えてしまいます。意味的に異なるものと勘違いしやすい構文は混乱を招きます。
let x = if cond then
some_long_expression
else
some_other_long_expression
提案された案にも利点と欠点があるように、?:
にも利点があります。[{}
の仕様をやめ Python のような字下げの記法を採用する] の節でも記載したように、変えることに強い利点がない場合は C 言語系統の慣習に従う判断がされています。
if/else, switch を式にする
前提
Swift では if/else, switch は文です。つまり、値を返させないため、下記のことができません。
let v: Int = if condition {
return something
} else {
return other
}
ちなみに、Scala, Kotlin, Rust では if/else, switch を式として扱えるので上記のことができます。
リジェクト理由
「提案されている変更を加えると文法が複雑化してしまう。それを許容するだけのユースケースがまだ無い。ユースケースとして挙げられている処理に対しても、既にその目的を実現するための機能がある。また、欠点もある。」とのことです。
“既にその目的を実現するための機能” というのは、例えば、「複数の経路(条件分岐中)で定数の初期化が可能」などの機能です。
let v: Int
if condition {
v = something
} else {
v = other
}
個人的には下記の方が意図が明確に表せると考え、提案されている構文に近い書き方にしています。(詳しい理由: Swift リファクタリング実践 Tips1 - Qiita)
let v: Int = {
if condition {
return something
} else {
return other
}
}()
guard
を unless
にリネームする
前提
guard
文のブロック中では、次の処理に進まないことを保証しなければなりません。
例えば、return
, break
, throw
, continue
などでスコープを抜けるか、fatalError()
で処理を中止させるなどです。
リジェクト理由
提案された unless
は if
文の逆という印象を受けます。
if cond { ... }
unless !cond { ... }
しかし、guard
と if
はそのような対応関係ではありません。
guard
はその先の処理の前提条件を宣言し、条件を満たしていない場合、その先の処理に進ませないことを保証します。
guard preCondition else {
// can't go next code path
}
まとめ
Swift では C 言語系統の言語 (C++、C#、Objective-C、Java、JavaScript) の仕様を判断基準の中心的な部分に置いているようです。「強い利点がなければ C 言語系統に近いものを採用する」という記述が多く見られました。理由として、「他の言語を行き来する人にとって親しみやすいことや、C 言語系統の言語にその文法が受け継がれ続けていることは偶然ではなく理由がある」と語られていました。
また、機能のもたらす利益と、パフォーマンスやコード解析に対する負荷とのバランスについての議論も各所で見られました。
全体的な傾向として「その変更が有用であると納得できるユースケース」を強く求めているように感じます。swift-evolution/proposals 内の proposal でも、利点や何をどのように改善できるのか詳しく書かれています。
そのほかに、下記のような発言もあり興味深かったです。
- ”簡潔さ” が目的ではなく、”表現力” を目的とする
- "don't be like C" が目的ではなく、もちろん、"Be like C" も目的ではない
- 文法を追加することで表現力を上げることにならない
- 簡潔さが表現力を上げるとも限らず、また、複雑さも表現力を向上しない
定期的に配信しているラジオ番組(podcast)でもこの話題をしてみました。この記事とラジオでそれぞれ少し違う話題展開になったかと思います。