名前付き引数
名前付き引数(Named Parameters/Named Arguments)は関数呼出し時の引数に対して引数名やラベル名を明示するための仕組みです。言語によってはラベル引数やキーワード引数、名前付きパラメータとも呼ばれています。
名前付き引数をサポートする言語では、関数呼び出し時の実引数に対して、仮引数の名前やラベル名/パラメータ名を明示することが可能になります。以下のコードのname
とage
がその例です。
// 名前付き引数による関数呼出し
printPerson(name: "Tom", age: 24);
// 通常の関数呼出し
printPerson("Tom", 24);
名前付き引数はC#やSwift言語等の比較的新しい言語でサポートされてします。
/* C#言語でのメソッド定義例 */
void printPerson(string name, int age) {
My.printf("%s (%d)", name, age)
}
/* Swift言語での関数定義例 */
func printPerson(name firstName: String, age: Int) -> Void {
My.printf("%s (%d)", firstName, age)
}
Swift言語では仮引数の名前とラベル名を区別することが可能です。上記の例ではname
がラベル名で、firstName
が仮引数名となります。
名前付き引数のメリット
引数名/ラベル名の明示は一見すると面倒で手間の多い作業に思えますが、引数の意味や目的が明確になり、コードが読みやすくなるというメリットが生まれます。
名前付き引数が無かった頃には、コメントで引数の意味を明確化するしかありませんでした。
printPerson(/*名前*/"Tom", /*年齢*/24);
こうすることで他のプログラマが初見でソースを読む際に、引数の意味をその場で瞬時に把握することができるようになります。引数の意味が明確に示されることで、Javadoc等のリファレンスを何度も読み返す手間が無くなるというメリットも生まれます。
いまいちメリットが理解されていない
上記の説明だけでは、いまいちメリットが分からないと思う方も多いでしょう。Objective-C言語が流行り出した頃にもJava界隈では冗長的で意味がないという感想や批判をよく聞きました。
ですが、これは実際に保守範囲の広い大規模なコードや保守頻度の少ないコードを書いたり、様々なライブラリを利用していると、名前付き引数の必要性が分かって来ます。
リファレンスを読み返す手間が無くなる
以下の例は実際の開発現場で様々なライブラリを活用していたり、他人のコードを修正している際にもよく遭遇する問題なのですが、例えば、とある関数の第一引数の役割は直感的に理解出来ても、第二引数や第三引数がどういう役割を持っているかよくわからないといったことがよく起こります。そのような場合は関数のリファレンスや関数の定義箇所を見返す必要が出てきます。
// 第三引数の役割がよくわからない。
join("hello", "world", "||");
// リファレンスやヘッダーファイルの宣言箇所を確認すれば
// separator(区切り文字)であることがわかるが・・・
// 毎回確認するのは面倒(忘れやすい人)
/**
* @left 右側の文字
* @right 左側の文字
* @separator 区切り文字
*/
String join(String left, String right, String separator);
// 以下のようにラベル名を明示出来れば
// わざわざリファレンスやヘッダーファイルの
// 宣言箇所を確認する必要は無くなる
join(left: "hello", right:"world", separator:"||"); // "hello||world"
2,3個の関数やオーバライド・メソッドであれば、その場で覚えられるかもしれませんが、実際にはもっと多くの関数やメソッドが存在するはずで、その全てを把握するのは困難です。結果、同じ関数の意味を何度も調べる羽目になるのです。
そこで名前付き引数の出番
しかし名前付き引数の仕組みによって関数呼び出し側でラベル名/引数名を明示出来れば、関数の意味を何度も確認するようなことは無くなります。ラベル名を見れば引数の意味は一目瞭然で、わざわざリファレンスを読み返す必要もなくなるためです。
ラベル名はある種の「ヒント」にもなりえるわけです。
名前付き引数には、引数の意味や目的を関数呼び出し側で把握できるようになるという優れたメリットがあります。
オーバーロードの欠点を補うことが出来る
特にオーバーロード・メソッドについては引数に渡す型によって異なる動作をすることがありますから、そのような多様性をラベル名で明確に明示出来るようになると、コードは劇的に理解しやすいものになります。
"xxxabc".find("abc", fromIndex:3)
"xxxabc".find("abc", backwardsSearch:true)
[a:"A", b:"B"].get(key:"a"); // "A"
[a:"A", b:"B"].get(index:1); // "B"
逆にラベル名を明示しないと、以下のような意図がはっきりとしない抽象的なコードになりがちです。これはある意味オーバーロードの欠点とも言えるでしょう。
"xxxabc".find("abc", 3)
"xxxabc".find("abc", true)
名前付き引数はこのオーバーロードが引き起こす曖昧さの欠点を補うことが出来ます。
連想配列による名前付きパラメータ・イディオムが不要になる
コードの可読性・保守性の向上を意識して使われることもある連想配列ですが、名前付き引数で代替できます。
// Before
new Person({
name: "tom",
age: 22,
adress: "Tokyo"
});
// After
new Person(
name: "tom",
age: 22,
adress: "Tokyo"
)
引数順序の入れ替えが実現出来る
ちなみに、言語によっては名前付き引数の仕組みによって、実引数の順序を順不同に並べることも可能になります。
// いずれも呼び出し先と結果は同じ
join(right:"B", left: "A", separator:"||"); // "A||B"
join(left: "A", separator:"||" right:"B"); // "A||B"
対応状況
最近のモダンな言語(Swift, C#, Ruby)の多くは言語機能として名前付き引数をサポートしていることが多いですが、JavaやC/C++等の言語ではまだサポートされていません。
ただ、既存言語で名前付き引数と同等の要件を実現するイディオムがいくつかありますので、紹介しておきます。
メソッドチェーンによる明示
Java言語やC++等のオブジェクト指向言語では比較的有名なイディオムです。
// 名前付きパラメータ・イディオム
Person p = new Person().setName("tom").setAge(18);
連想配列による明示
連想配列/マップをサポートするJavaScript等の言語では、連想配列のキーを活用することで、名前付き引数と同等の要件を満たすことが出来ます。
var p = new Person({name: "bob", age: 18});
function Person(params) {
this.name = params.name;
this.age = params.age;
}
構造体メンバ名による名前付き引数イディオム
C言語や一部のC++処理系については、指定初期化子と複合リテラルという機能の組み合わせによって、名前付き引数と同等の仕組みを実現することが可能です。
Person({.name = "tom", .age = 99});