Removing currying func declaration syntax
01 何が問題だったのか
初期のSwiftには、ML系の言語から取り入れた「カリー化された関数宣言構文」がありました。引数リストを複数並べて書くことで、部分適用可能な関数をそのまま宣言できる機能です。
func curried(x: Int)(y: String) -> Float {
return Float(x) + Float(y)!
}
この curried は Int を受け取って (String) -> Float を返す関数として振る舞い、curried(1) のように部分適用できます。しかし、Swiftの実際のイディオムにおいてこの機能の恩恵は小さく、一方で言語と実装に無視できない複雑さを持ち込んでいました。
言語機能としての整合性が取りづらい
カリー化構文は、他の言語機能と素直に噛み合いません。
- 2番目以降の引数リストに現れるキーワードや宣言名のルールをどう解釈するか(既存引数リストの続きなのか、新しい関数の引数リストなのか、別ルールなのか)について、設計上の議論が繰り返されてきました。
inout引数を最初の引数リスト以外に置いた場合、驚くような意味的制約なしには部分適用できず、実用性が損なわれます。var引数はどの段階の部分適用で束縛するかが直感に反しやすく、多くの利用者は最外側での束縛を期待するのに対し、実装は最内側で束縛していました。
Swiftのイディオムに合わない
標準ライブラリやCocoa、そして多くのサードパーティコードでは、自由関数をML流にカリー化して使うスタイルはほとんど見られません。メソッド中心のAPI設計では、self.method による部分適用や、クロージャを使った { f($0) } のような書き方で十分事足ります。
加えて、カリー化構文はSwiftのキーワード引数モデルよりも前に設計されたもので、「引数は単一のタプル」という古いモデルを前提にしていました。@autoclosure や inout の存在がすでにそのモデルから外れていることもあり、言語の方向性はML流の引数モデルから離れつつありました。
利用者からの評価も低い
関数型志向の利用者からも「使いどころがない」「むしろScala流の f(_, 1) のようなアドホックな部分適用の方が欲しい」といった声が挙がっており、「もし今この機能がなかったとして、これから追加したいか?」という観点でも正当化しにくい状況でした。
02 どのように解決されるのか
func 宣言から複数の引数リストを書ける構文を取り除き、引数リストは1つだけに制限します。これにより、カリー化された関数は func の文法の一部としては表現できなくなります。
移行方法
既存のカリー化された関数は、クロージャを明示的に返す形に書き換えることで等価な挙動を再現できます。
// Before:
func curried(x: Int)(y: String) -> Float {
return Float(x) + Float(y)!
}
// After:
func curried(x: Int) -> (String) -> Float {
return {(y: String) -> Float in
return Float(x) + Float(y)!
}
}
呼び出し側は curried(x: 1)("2") のように、戻り値のクロージャをそのまま適用する形になります。部分適用したい場合は、let partial = curried(x: 1) のように戻り値を受け取ってから使えます。
メソッドの扱いは変わらない
この提案で変わるのは func 宣言の構文のみで、メソッドの型の扱いは従来どおりです。メソッドは形式的には Self -> Args -> Return という型を持ち、self.method による部分適用は引き続き利用できます。
既存コードへの影響
言語機能の削除なので、カリー化構文を使っているコードは当然コンパイルできなくなります。ただし上記のように機械的な書き換えが可能で、機能の有用性が限定的だったことを踏まえ、言語を簡潔に保つためのトレードオフとして受け入れられました。