Standardize function type argument syntax to require parentheses
01 何が問題だったのか
Swift の関数型は、パラメータリストを括弧でくくって書くのが基本です。関数宣言や関数呼び出しの構文とも揃っていて、次のような型はいずれも括弧を伴います。
(Int) -> Float
(String) -> Int
(T) -> U
(Int) -> (Float) -> String
ところが Swift 2 までは、「ラベルのない・可変長ではない・属性も付かない単一パラメータ」という特殊なケースに限って、パラメータ側の括弧を省略する書き方が許されていました。
Int -> Float
String -> Int
T -> U
Int -> Float -> String
文法上の曖昧さを生む
この括弧省略は純粋なシンタックスシュガーで、表現力を追加するものではありません。しかし省略を許すことで、関数型の読み方に曖昧さが持ち込まれていました。
() -> Int // 引数なしか、それとも「引数なしのパラメータ」を1つ取るのか?
(Int, Float) -> Int // 2引数か、それとも「タプル (Int, Float)」を1つ取るのか?
これらを区別するために専用のルールが必要で、文法の整合性を損なっていました。
宣言・呼び出しと食い違う
関数宣言も関数呼び出しもパラメータ部には必ず括弧が付きます。関数型だけが単一引数の場合に括弧を落とせるというのは、言語内で一貫性のない例外扱いでした。
func f(a: Int) { ... } // 宣言には括弧が要る
func f a: Int { ... } // これは当然書けない
歴史的な経緯でしか残っていなかった
この省略形は、Swift のごく初期の「関数はすべて単一の値(しばしばタプル)を受け取り単一の値を返す」というモデルの名残でした。その後、パラメータリストには可変長引数・デフォルト引数・内部引数名と API ラベルの区別など、タプルでは表現できない機能が次々に加わり、実装上も関数パラメータをタプルとして扱うのはやめていました。モデルが変わったあとも省略形だけが残っていた、という状態です。
さらに、この省略形を取り除くのであれば Swift 3 のタイミングが最後のチャンスでした。Swift 3 では別件でも大きなソース変更が入る予定で、マイグレータによる一括書き換えが可能です。Swift 3 を過ぎると既存コードへの影響が大きくなり、後から除去するのは難しくなります。
02 どのように解決されるのか
関数型のパラメータ部には常に括弧を必須とします。単一パラメータであっても省略は許されません。Swift 2 までの T -> U 形式はコンパイルエラーになり、(T) -> U と書くことに統一されます。
Int -> Int // error
(Int) -> Int // Int から Int への関数
((Int)) -> Int // こちらも Int から Int への関数(二重括弧は単に括弧でくくっただけ)
Int, Int -> Int // error
(Int, Int) -> Int // Int と Int の2引数を取り Int を返す関数
((Int, Int)) -> Int // タプル (Int, Int) を単一引数に取り Int を返す関数
これで、見た目から引数の個数が一意に定まるようになります。たとえば () -> Int は常に「引数なし」、(Int, Float) -> Int は常に「2引数」と読めます。タプルを単一引数として受け取りたい場合は、二重の括弧で ((Int, Float)) -> Int と明示的に書きます。
引数の個数は呼び出し側の括弧にそのまま対応する
パラメータ部を括弧で書くようになった結果、関数型の見た目と呼び出しの書き方がきれいに対応します。
let f: () -> Int // 引数なしの関数
let g: (()) -> Int // () を単一引数に取る関数
let h: ((())) -> Int // () を単一引数に取る関数(二重括弧はくくっただけ)
f(); g(()); h(()) // 正しい
f(()); g(); h() // いずれもエラー
関数宣言・呼び出しとの一貫性
関数宣言・関数呼び出し・関数型のすべてで、パラメータ部は括弧でくくる、という一貫したルールに揃います。関数型の文法は次の形です。
- function-type →
(function-type-parametersopt)throws-annotationopt->type - function-type-parameter には属性や
inout、可変長(...)が付けられる
マイグレーションは自動
Swift 2 から Swift 3 へのマイグレータが、省略形で書かれていた関数型に括弧を自動で補います。既存コードに Int -> Int のような型があっても、マイグレーション時に (Int) -> Int へ書き換えられるため、手作業で直す必要はありません。
戻り値やクロージャ式には影響しない
この提案で括弧が必須になるのはあくまで関数型のパラメータ部です。関数の戻り値の型や、クロージャ式のパラメータリスト({ x, y in ... } のような短縮形)には影響しません。戻り値は宣言でも括弧を付けない慣習があり、クロージャ式は短い記法が表現力の要なので、いずれも従来どおりの書き方が使えます。