Swift Digest
SE-0021 | Swift Evolution

Naming Functions with Argument Labels

Proposal
SE-0021
Authors
Doug Gregor
Review Manager
Joe Groff
Status
Implemented (Swift 2.2)

01 何が問題だったのか

Swiftでは、関数やメソッドはそれ自体をファーストクラスの値として扱え、関数型の変数や定数に代入できます。しかし関数名を書いて参照するときには、ベース名のみ しか書けず、引数ラベルを含めた完全な名前では参照できないという制約がありました。

オーバーロードされた関数を曖昧さなく参照できない

たとえば UIView には、同じベース名 insertSubview を持つ3つのメソッドがあります。

extension UIView {
  func insertSubview(view: UIView, at index: Int)
  func insertSubview(view: UIView, aboveSubview siblingSubview: UIView)
  func insertSubview(view: UIView, belowSubview siblingSubview: UIView)
}

呼び出すときは引数ラベルで区別できるのですが、これを関数値として取り出そうとすると、どのメソッドを指しているのか決められずに曖昧になってしまいます。

let fn = someView.insertSubview // ambiguous: could be any of the three methods

型注釈で絞り込める場合もありますが、これで解決できるのは引数の型が異なるオーバーロードに限られます。上の例のように、後ろ2つは引数の型がどちらも (UIView, UIView) で同じなので、型だけでは区別できません。

let fn: (UIView, Int) = someView.insertSubview    // ok: uses insertSubview(_:at:)
let fn: (UIView, UIView) = someView.insertSubview // error: still ambiguous!

こうなると、参照を得るためだけにわざわざクロージャでラップするしかなく、記述が冗長で退屈なものになっていました。

let fn: (UIView, UIView) = { view, otherView in
  someView.insertSubview(view, aboveSubview: otherView)
}

Objective-Cセレクタを表現する手段にも波及する

もうひとつの動機として、Swiftには将来「メソッドからObjective-Cのセレクタを得る」ような操作を追加したいという議論がありました。そうした機能の引数にはメソッドへの参照を渡すのが自然ですが、引数ラベルまで含めてメソッドを指定できないと、insertSubview(_:aboveSubview:)insertSubview(_:belowSubview:) を区別して渡すこと自体ができません。メソッドを曖昧さなく名指しできる仕組みが、こうした周辺機能の前提としても不足していました。

02 どのように解決されるのか

関数名を書ける場所ならどこでも、ベース名と引数ラベルを組み合わせた コンパウンド名(compound name)を書けるようにします。具体的には、insertSubview(_:aboveSubview:) のように、ベース名の直後に ( ラベル: ラベル: ) の形で引数ラベル列を添えて書きます。ラベルを省略する引数位置には _ を置きます。

オーバーロードを曖昧さなく参照する

これまで曖昧で取り出せなかった関数参照も、コンパウンド名を使えば一意に指定できます。

let fn = someView.insertSubview(_:at:)
let fn1 = someView.insertSubview(_:aboveSubview:)

イニシャライザに対しても同じ形式が使えます。

let buttonFactory = UIButton.init(type:)

全引数ラベルを書く必要がある

参照名には、宣言時に存在するすべての引数ラベルを列挙する必要があります。デフォルト引数や可変長引数を持つ関数でも、その位置のラベルを省略することはできません。

func foo(x x: Int, y: Int = 7, strings: String...) { ... }

let fn1 = foo(x:y:strings:) // okay
let fn2 = foo(x:)           // error: no function named 'foo(x:)'

ゼロ引数関数の扱い

引数ラベルのリストが空の場合は、f() がその関数の呼び出しと解釈されるため、コンパウンド名としては書けません。引数を取らない関数を関数値として取り出したいときは、従来どおり型注釈などのコンテキスト情報で絞り込む必要があります。

既存コードへの影響

これは純粋な追加機能なので、既存のコードの挙動は何も変わりません。今まで書けなかった書き方が新たに使えるようになるだけです。