Swift 2.2 Released! より SE-0021: 引数ラベル付きで関数の名前付け です。
原文に従ってこの訳は Apache License 2.0 とします。
ツッコミお待ちしています。
引数ラベル付きで関数の名前付け
- 提案: SE-0021
- 著者: Doug Gregor
- ステータス: 受理された (Swift 2.2 で実装された)
- レビューマネージャ: Joe Groff
はじめに
Swift は第一級関数をサポートしています。このためどんな関数(またはメソッド)でも関数型の値をとる箇所に記述することができます。しかし、関数の名前を指定する際、基本名(例えば insertSubView
)だけを与え、引数ラベルは与えません。これはオーバーロードされた関数は型情報で区別をつけなければならないことを意味しますが、扱いにくく冗長になります。この提案では関数を指定する際に引数ラベルを与えられるようにして、ほとんどのケースで型情報を与える必要をなくします。
Swift-evolution スレッド: この提案の最初のドラフトはこちらで議論されました。ゲッター/セッターの名前付け(元はこちらで Michael Henson によって提案され、こちらで議論されたものです)もサポートしていました。Joe Groff のメッセージでレンズの方がゲッター/セッターに対するよりよりアプローチだと確信し、このバージョンの提案からは削除しました。
動機
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)
}
これらのメソッドを呼び出すとき、引数ラベルによって異なるメソッドを区別します:
someView.insertSubview(view, at: 3)
someView.insertSubview(view, aboveSubview: otherView)
someView.insertSubview(view, belowSubview: otherView)
しかし、関数値を作るために関数を指定する場合、ラベルを与えることはできません:
let fn = someView.insertSubview // ambiguous: could be any of the three methods
型注釈を使って曖昧さを解消できる場合があります:
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
button.insertSubview(view, aboveSubview: otherView)
}
これは嫌になる程うんざりします。
もうひとつ、ついでの動機として: おそらく Swift にはあるメソッドに対する Objective-C のセレクタを取得する何らかの方法が必要なはずです(文字列で指定するんじゃなくて)。そのような操作の引数はメソッドの指定のようになり、ゲッターとセッターを含むどのようなメソッドにも名前をつけられることが役に立つでしょう。
提案手法
名前を記述できる箇所ではどこでも複合的な Swift での名前(例えば insertSubview(_:aboveSubview:)
)を許すように関数の名前のつけ方を拡張することを提案します。具体的には以下のようになります:
let fn = someView.insertSubview(_:at:)
let fn1 = someView.insertSubview(_:aboveSubview:)
同じ文法でイニシャライザを指定することもできます。例えば、
let buttonFactory = UIButton.init(type:)
"与えられたメソッドの Objective-C のセレクタを作る" という操作は単独の提案のテーマになるでしょう。しかしここ提案する文法がどのように使われることになるかひとつの可能性を示しておきましょう。
let getter = Selector(NSDictionary.insertSubview(_:aboveSubview:)) // produces insertSubview:aboveSubview:.
詳細設計
文法的には primary-expression の文法が:
primary-expression -> identifier generic-argument-clause[opt]
から:
primary-expression -> unqualified-name generic-argument-clause[opt]
unqualified-name -> identifier
| identifier '(' ((identifier | '_') ':')+ ')'
に変わるでしょう。
括弧の中の "+" の使用は重要です。これにより:
f()
が引数のない f
の参照ではなく、f
の呼び出してあることが明確になるからです。引数がない関数の参照は引き続き文脈的な型情報によって曖昧さを取り除くことが必要です。
名前の指定には宣言に現れる全ての引数を含めなければならない点には注意が必要です。デフォルト引数を持つものや、可変長引数を省略することはできません。例えば:
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:)'
既存のコードに対する影響
これは純粋に付加的な機能であるため、既存のコードに対する影響はありません。
検討された代替案
Joe Groff によるプロパティに対して操作を行いたいという場合に手動でゲッター/セッター関数を取得するよりもより良い方法となるレンズについてのノート。
Bartlomiej Cichosz による
_
をプレースホルダとして使った一般化した部分適用文法の提案。例えば:
aGameView.insertSubview(_, aboveSubview: playingSurfaceView)
全ての引数が _
である場合に、これはメソッドに名前をつける機能を提供します:
aGameView.insertSubview(_, aboveSubview: _)
私はこの方法を使おうとは思いませんでした。このような一般化した部分適用文法が必要だとは思わないからです。$ 名はほぼ簡明であり、_
プレースホルダが引数をどのように部分適用された関数に割りあてるのかという疑問生みません:
{ aGameView.insertSubview($0, aboveSubview: playingSurfaceView) }
- 名前の中のアンダースコアを省略することもできました。例えば:
let fn1 = someView.insertSubview(:aboveSubview:)
しかし、これは最初の引数にラベルがないメソッドとあるメソッドの見た目上の区別を難しくしてしまいます。例えば f(x:)
と f(:x:)
のように。それに加え、システム内のいたるところ(例えば、Clang の swift_name
属性)で関数名の中の空の引数ラベルがアンダースコアを使って記述されています。この一貫性は維持されるべきです。