Remove implicit tuple splat behavior from function applications
01 何が問題だったのか
初期のSwiftでは、関数呼び出しに「通常の引数渡し」と「タプルをまとめて実引数リストとして渡す形(通称 tuple splat)」の二つのスタイルが暗黙に共存していました。たとえば次の foo は、引数を一つずつ書く形でも、実引数と同じ形のタプルを一度に渡す形でも呼び出せます。
func foo(a: Int, b: Int) {}
foo(42, b: 17) // 通常の呼び出し
let x = (1, b: 2)
foo(x) // tuple splat: タプルを展開してパラメータに割り当てる
この tuple splat は純粋なシンタックスシュガーで、通常の呼び出し以上の表現力はありません。一方で、言語と実装の双方にいくつもの問題を引き起こしていました。
通常の呼び出しと見分けがつかない
foo(x) という書き方は、foo のオーバーロードに引数を一つ渡しているようにも見えます。コンパイラにとっても人間にとっても、通常の呼び出しか tuple splat かを区別するのが難しく、機能の存在を知らない読み手にとっては非常に紛らわしいものでした。
文法上の曖昧さを抱えていた
Any 型の引数や、単一のパラメータとしてタプルを渡したい場面と衝突し、本当に曖昧な文法ケースが発生していました。コンパイラがどちらの解釈を取るべきか判断しきれない状況が実装上の悩みとして残っていました。
実装が不安定で型チェッカを複雑にしていた
tuple splat の実装には多数のバグがあり、常に期待通りに動くとは言えない状態でした。加えて、この機能を支えるために型チェッカが複雑化し、コンパイル速度とメンテナンス性の両面でコストになっていました。
「あるべき tuple splat」としても機能していなかった
本来なら、タプルを返す関数の戻り値をそのまま別の関数に渡せると嬉しい場面があります。
func bar() -> (Int, Int) { ... }
foo(bar()) // エラー: ラベルが一致しないので splat できない
しかし実際には、タプル側のラベルがパラメータ名と一致していないと splat できないため、呼び出し側は次のような不自然な工夫を強いられていました。
- パラメータ名をすべて
_にして命名規約を崩す - 手動で並べ替えるなどしてシュガーの利点を打ち消す
- 戻り値のタプルにラベルを付けて呼び出し元と呼び出し先を密結合にする
歴史的な経緯で残っていただけの機能
tuple splat は、Swiftごく初期(2010〜2011年頃)の「すべての関数呼び出しは関数型に対して単一の値を渡す」というモデルの名残でした。inout、デフォルト引数、可変長引数、ラベルなどの導入で Swift 全体はそのモデルから離れていたにもかかわらず、tuple splat だけが見直されないまま残っていた、という位置づけです。「もし今この機能がなかったとして、Swift 3 に追加したいか?」という観点で正当化できない機能でした。
02 どのように解決されるのか
関数呼び出しに対する暗黙の tuple splat を言語から削除します。Swift 3 のコンパイラでは、従来どおりパースと型チェックは行いつつ、tuple splat の形になっている呼び出しはエラーとし、Fix-it で通常の呼び出しへの書き換えを促します。マイグレータもこの Fix-it を自動適用するため、既存コードは機械的に追従できます。
書き換え方
タプル値をそのまま渡していた箇所は、要素ごとに展開して渡す形に書き換えます。
func foo(a: Int, b: Int) {}
let x = (1, b: 2)
// Before:
foo(x)
// After:
foo(x.0, b: x.1)
// もしくは
foo(x.0, b: x.b)
x が複雑な式の場合は、いったん一時変数に束縛してから各要素を取り出すのが無難です。
タプルを「1つの引数」として渡すのは従来どおり
この提案が取り除くのは、一つのタプル値を複数のパラメータへ暗黙に展開するケースだけです。タプルを単一のパラメータとしてそのまま受け渡すのは引き続き問題なく動きます。
func f1(a: (Int, Int)) { ... }
let x = (1, 2)
f1(x) // OK: a にタプルをそのまま渡すだけ
func f2<T>(a: T) -> T { ... }
f2(x) // OK: ジェネリックパラメータにタプル型が束縛される
混乱しがちなポイントですが、「パラメータがタプル型1個」と「パラメータが複数あってタプルをまとめて渡す」は別物で、後者のみが今回の対象です。
将来の tuple splat を否定するものではない
提案では、明示的な構文を持った「きちんと設計された tuple splat」が将来ありうるかもしれない、という余地が残されています。たとえば架空の foo(*x) のような専用構文であれば、通常の呼び出しとの曖昧さは解消できます。ただし、
- tuple splat は純粋なシンタックスシュガーで優先度が低い
- 適切な記号の選定(
*は将来のメモリ関係の用途に取っておきたい)や、ラベル・可変長引数との相互作用を含めた意味論の再設計が必要
といった理由から、これは Swift 3 のスコープには含めず、必要であれば後続提案として別途検討する、という整理になっています。現時点で tuple splat 相当の書き方が必要な場合は、要素を明示的に展開して渡す通常の呼び出しを使います。