Swift Digest
SE-0066 | Swift Evolution

Standardize function type argument syntax to require parentheses

Proposal
SE-0066
Authors
Chris Lattner
Review Manager
Doug Gregor
Status
Implemented (Swift 3.0)

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 ... } のような短縮形)には影響しません。戻り値は宣言でも括弧を付けない慣習があり、クロージャ式は短い記法が表現力の要なので、いずれも従来どおりの書き方が使えます。