Establish consistent label behavior across all parameters including first labels
01 何が問題だったのか
Swift 2 では、関数・メソッド宣言の引数ラベルの扱いが、最初の引数だけ特別 でした。2番目以降の引数は、内部名をそのまま外部ラベルとして公開するのに対し、先頭の引数はラベルが自動的に省略される、という二段構えのルールです。
func foo(a: T, b: U, c: V)
この宣言は、一見すると foo(a:b:c:) を定義しているように見えますが、実際に定義されるのは foo(_:b:c:) でした。つまり、呼び出し側では最初の引数だけラベルなしで渡すことになります。
foo(x, b: y, c: z) // a: は書けない
イニシャライザとの不一致
一方で、イニシャライザは以前からすべての引数のラベルが対等に扱われており、先頭も例外ではありませんでした。同じ「引数リスト」を書いているのに、関数・メソッドとイニシャライザで最初の引数の振る舞いが食い違うのは、言語としての一貫性を欠いていました。
API設計ガイドラインとのずれ
Swift 3 で新しくなったAPI設計ガイドラインは、先頭引数にもラベルを積極的に与える方針を打ち出しました。たとえば次のようなケースでは、先頭にラベルを付けるほうが読みやすくなります。
- 先頭引数にデフォルト値がある場合
- 先頭引数が前置詞句を伴う場合(
with:/from:など) - ファクトリメソッド
- 1つの抽象概念を複数の引数に分割して渡す場合
また、Objective-C APIをSwiftに取り込む自動変換ルール(SE-0005)も、この方向に合わせて改訂されました。こうしてガイドライン側が「先頭ラベルを使おう」という方向に舵を切った結果、「先頭だけラベルを落とす」という言語の既定動作は、推奨されるAPIスタイルと真っ向から衝突するようになってしまいました。
回避策の煩雑さ
Swift 2 でも、先頭にラベルを付けたいときは、外部名を明示的に2度書く(外部名と内部名を並べる)ことで実現できました。
func foo(x x: Int, y: Int) // foo(x:y:) を定義
しかしこの「同じ名前を2度書く」スタイルは冗長で、普通に書いたときの挙動と食い違うため、ラベルを付けたいだけなのに書き手にも読み手にも負担がかかっていました。
02 どのように解決されるのか
関数・メソッド宣言における先頭引数の扱いを、2番目以降の引数と揃えます。ラベルはすべての位置で一様に機能し、内部名がそのまま外部ラベルになります。イニシャライザと同じ挙動です。
基本の挙動
次の宣言は、Swift 3 以降では foo(x:y:) を定義します(Swift 2 では foo(_:y:) でした)。
func foo(x: Int, y: Int)
foo(x: 1, y: 2) // OK
先頭ラベルを公開するために内部名を2度書く、といった工夫はもう不要です。
ラベルを省略したいとき
従来どおり、外部名の位置に _ を置けば、先頭引数のラベルを明示的に省略できます。
func foo(_ x: Int, y: Int) // foo(_:y:) を定義
foo(1, y: 2)
外部名と内部名を別にしたいとき
外部ラベルと内部名を分けたい場合も、従来と同じ書き方です。外部名を内部名の前に、空白区切りで並べます。
func foo(xx x: Int, yy y: Int) // foo(xx:yy:) を定義
foo(xx: 1, yy: 2)
呼び出し側とセレクタ表記
呼び出し側の書き方は変わりません。foo(2, y: 3) のように、先頭引数にラベルを付けない呼び出しがこれまでと同じように動くよう、移行ツールは宣言側に _ を挿入する方向でコードを書き換えます(func foo(x: Int, y: Int) は func foo(_ x: Int, y: Int) になります)。
また、#selector によるセレクタ表記(#selector(ViewController.foo(_:y:)) など)も変更されません。
サブスクリプトへの影響はなし
サブスクリプトは型の要素にアクセスするための構文であり、関数・メソッドの引数ラベルとは役割が異なります。サブスクリプトのラベルの扱いは従来どおり変わらず、必要に応じて自由にラベルを付けたり付けなかったりできます。