名前付きパラメーターにより、コードが読みやすくなり、記述しにくくなります
コードを読んでいるとき、名前付きパラメーターは、コードを理解しやすくするコンテキストを導入できます。たとえば、次のコンストラクタを検討してくださいColor(1, 102, 205, 170)
。それは一体何を意味するのでしょうか?確かに、Color(alpha: 1, red: 102, green: 205, blue: 170)
はるかに読みやすくなります。しかし残念ながら、コンパイラは「ノー」と言う -それは望んでいますColor(a: 1, r: 102, g: 205, b: 170)
。名前付きパラメーターを使用してコードを記述する場合、正確な名前を調べるのに不必要な時間を費やします。一部のパラメーターの正確な名前を忘れる方が、順序を忘れるよりも簡単です。
DateTime
ほぼ同一のインターフェースを使用して、ポイントと期間に2つの兄弟クラスを持つAPIを使用するときに、これはかつて私に噛みつきました。一方でDateTime->new(...)
受け入れられたsecond => 30
引数を、DateTime::Duration->new(...)
望んでいたseconds => 30
、と他のユニットについても同様。はい、それは絶対に理にかなっていますが、これは名前付きパラメーター≠使いやすさを私に示しました。
悪い名前は読みやすくさえしない
名前付きパラメーターがいかに悪いかという別の例は、おそらくR言語です。このコードはデータプロットを作成します。
plot(plotdata$n, plotdata$mu, type="p", pch=17, lty=1, bty="n", ann=FALSE, axes=FALSE)
xおよびyデータ行の2つの位置引数、および名前付きパラメーターのリストが表示されます。デフォルトにはさらに多くのオプションがあり、デフォルトを変更または明示的に指定したいオプションのみがリストされています。このコードでマジックナンバーを使用し、列挙型を使用することでメリットが得られることを無視すると(Rがあれば!)、問題はこれらのパラメーター名の多くがかなり判読できないことです。
pch
は実際にはプロット文字であり、すべてのデータポイントに対して描画されるグリフです。17
空の円、またはそのようなものです。
lty
はラインタイプです。これ1
が実線です。
bty
ボックスタイプです。"n"
プロットの周りにボックスが描画されないように設定します。
ann
軸注釈の外観を制御します。
各略語の意味がわからない人にとって、これらのオプションはかなり混乱します。これは、Rがこれらのラベルを使用する理由も明らかにします。自己文書化コードとしてではなく、(動的に型付けされた言語である)キーとして値を正しい変数にマッピングします。
パラメータと署名のプロパティ
関数シグネチャには次のプロパティがあります。
- 引数は順序付けすることも順序付けすることもできませんが、
- 名前付きまたは名前なし、
- 必須またはオプション。
- 署名はサイズや種類によってオーバーロードすることもできますが、
- 可変引数を使用してサイズを指定できません。
異なる言語は、このシステムの異なる座標に到達します。Cでは、引数は順序付けられ、名前がなく、常に必須であり、可変引数にすることができます。Javaでも状況は似ていますが、署名がオーバーロードされる可能性があります。Objective Cでは、署名は順序付けられ、名前が付けられ、必須であり、Cの周りの単なる構文糖であるため、オーバーロードすることはできません。
可変引数(コマンドラインインターフェイス、Perlなど)を使用して動的に型指定された言語は、オプションの名前付きパラメーターをエミュレートできます。署名サイズがオーバーロードされている言語には、位置のオプションパラメーターのようなものがあります。
名前付きパラメーターを実装しない方法
名前付きパラメーターについて考える場合、通常、名前付きのオプションの無秩序パラメーターを想定します。これらの実装は困難です。
オプションのパラメータにはデフォルト値を設定できます。これらは、呼び出された関数によって指定する必要があり、呼び出し元のコードにコンパイルしないでください。そうしないと、すべての依存コードを再コンパイルせずにデフォルトを更新できません。
ここで重要な問題は、引数が実際に関数にどのように渡されるかです。順序付けられたパラメータを使用すると、引数をレジスタに渡すか、スタック上の固有の順序で渡すことができます。レジスタをしばらく除外すると、問題は順序付けられていないオプションの引数をスタックに配置する方法です。
そのためには、オプションの引数に対して何らかの順序が必要です。宣言コードが変更された場合はどうなりますか?順序は無関係であるため、関数宣言の順序を変更しても、スタック上の値の位置は変更されません。また、新しいオプションのパラメーターを追加できるかどうかを検討する必要があります。ユーザーの観点からすると、これはそのように見えます。これは、以前はそのパラメーターを使用しなかったコードが新しいパラメーターで引き続き機能するためです。そのため、宣言での順序の使用やアルファベット順の使用などの順序は除外されます。
サブタイピングとリスコフ置換の原則にも照らしてこれを考慮してください。コンパイルされた出力では、同じ命令が、新しい名前付きパラメーターの可能性のあるサブタイプとスーパータイプでメソッドを呼び出すことができます。
可能な実装
最終的な順序を設定できない場合、順序付けされていないデータ構造が必要です。
最も単純な実装は、パラメーターの名前と値を単に渡すことです。これは、名前付きパラメーターがPerlまたはコマンドラインツールでエミュレートされる方法です。これにより、上記のすべての拡張機能の問題が解決されますが、スペースを浪費する可能性があり、パフォーマンスが重要なコードのオプションではありません。また、これらの名前付きパラメータの処理は、単にスタックから値をポップするよりもはるかに複雑になりました。
実際には、文字列プーリングを使用することでスペース要件を削減できます。これにより、後の文字列比較をポインター比較に減らすことができます(静的文字列が実際にプールされることが保証できない場合を除き、その場合、2つの文字列を比較する必要があります)詳細)。
代わりに、名前付き引数の辞書として機能する巧妙なデータ構造を渡すこともできます。キーのセットは呼び出し場所で静的に知られているため、これは呼び出し側で安価です。これにより、完全なハッシュ関数を作成したり、トライを事前計算したりできます。呼び出し先は、すべての可能なパラメーター名の存在をテストする必要がありますが、これはやや高価です。このようなものはPythonによって使用されます。
だから、ほとんどの場合、あまりにも高価です
名前付きパラメーターを持つ関数が適切に拡張可能である場合、最終的な順序付けは想定できません。そのため、2つのソリューションしかありません。
- 名前付きパラメータの順序を署名の一部にし、後の変更を許可しません。これは、自己文書化コードに役立ちますが、オプションの引数には役立ちません。
- キーと値のデータ構造を呼び出し先に渡します。呼び出し先は、有用な情報を抽出する必要があります。これは比較では非常に高価であり、通常はパフォーマンスを重視せずにスクリプト言語でのみ見られます。
その他の落とし穴
関数宣言内の変数名は通常、内部的な意味を持ち、多くのドキュメントツールで表示される場合でも、インターフェイスの一部ではありません。多くの場合、内部変数とそれに対応する名前付き引数には異なる名前が必要です。名前付きパラメーターの外部から見える名前の選択を許可しない言語は、変数名が呼び出しコンテキストを念頭に置いて使用されない場合、それらの多くを獲得しません。
名前付き引数のエミュレーションの問題は、呼び出し側の静的チェックの欠如です。これは、引数の辞書を渡すときに忘れがちです(Pythonを見て)。辞書を渡すことは一般的な回避策であるため、これは重要ですfoo({bar: "baz", qux: 42})
。ここでは、値のタイプも特定の名前の有無も静的にチェックできません。
名前付きパラメーターのエミュレート(静的型付け言語)
単純に文字列をキーとして使用し、オブジェクトを値として使用することは、静的型チェッカーの存在下ではあまり役に立ちません。ただし、名前付き引数は、構造体またはオブジェクトリテラルでエミュレートできます。
// Java
static abstract class Arguments {
public String bar = "default";
public int qux = 0;
}
void foo(Arguments args) {
...
}
/* using an initializer block */
foo(new Arguments(){{ bar = "baz"; qux = 42; }});