多くの言語が名前付きパラメーターをサポートしないのはなぜですか?[閉まっている]


14

関数を呼び出すときに、次のように書くことができれば、コードの読み取りがどれほど簡単になるかを考えていました。

doFunction(param1=something, param2=somethingElse);

欠点は考えられませんし、コードがずっと読みやすくなります。配列を唯一の引数として渡し、配列キーをパラメーター名として使用できることはわかっていますが、それでも読み取りは難しくなります。

私が見逃しているこの欠点はありますか?そうでない場合、なぜ多くの言語がこれを許可しないのですか?


1
名前付きパラメーターを持つべきであるが、持たない言語を与えることができますか?
ブライアンチェン

1
多くの場合、冗長すぎると思います。私が見た限りでは、言語がこれを使用するかどうかは、それが言語に良い影響を与えるか悪い影響を与えるかに依存する傾向があります。ほとんどのCスタイル言語は、それなしでうまく機能します。彼らの場合、とにかく何が起こっているのかがはっきりしていることがよくあり、その不在は本当に一般的に混乱を減らすのに役立ちます。
パンツァークライシス

2
@SteveFallows:「Named Parameter Idiom」は、Builderクラスで(Martin Fowlerによって命名された)流れるようなAPIを定義するための奇妙な名前のようです。Joshua BlochのEffective Javaの最初の項目の1つで同じパターンの例を見つけることができます。
ジョミナル

3
これは必ずしも意見に基づく質問ではありません
Catskul 14年

2
同意した。「なぜ多くの言語が名前付きパラメーターをサポートしないのですか?」意見よりも言語の歴史の問題です。誰かが「パラメータに名前を付けたほうがいいのではないか?」と尋ねたら意見です。
アンディフレミング

回答:


14

パラメーター名を指定しても、コードが読みやすくなるとは限りません。例として、読みたいですmin(first=0, second=1)min(0, 1)

前の段落の引数を受け入れた場合、パラメーター名の指定が必須ではないことは明らかです。すべての言語がパラメーター名の指定を有効なオプションにしないのはなぜですか?

少なくとも2つの理由が考えられます。

  • 一般に、すでにカバーされているニーズ(ここではパラメーターを渡す)に2番目の構文を導入することは、導入された言語の複雑さ(言語の実装と言語のプログラマーマインドモデルの両方)の固有のコストとバランスをとる必要がある利点です;
  • パラメーター名の変更をAPIの破壊的な変更にします。これは、パラメーター名の変更は些細な、非破壊的な変更であるという開発者の期待に適合しない可能性があります。

オプションのパラメーター(名前付きパラメーターが必要)を実装せずに名前付きパラメーターを実装する言語を知らないことに注意してください。そのため、Fluent Interfacesの定義で一貫して取得できる読みやすさの利点を過大評価することに注意する必要があります。


6
私は定期的にいくつかの異なる言語でコーディングしています。ほとんどの場合、特定の言語で単純なサブストリングのパラメーターを検索する必要があります。substring(start、length)またはsubstring(start、end)であり、文字列にendが含まれていますか?
ジェームズアンダーソン

1
@JamesAnderson-名前付きパラメーターの部分文字列はどのように問題を解決しますか?2番目のパラメーターの名前が何であるかを知るために、パラメーターを調べる必要があります(両方の関数が存在し、言語のポリモーフィズム機能が異なる名前の同じ型パラメーターを許可する場合を除く)。
mouviciel

6
@mouviciel:名前付きパラメーターはライターにとってはあまり使用されませんが、リーダーにとっては素晴らしいものです。読み取り可能なコードを作成するために変数名がどれほど重要かを知っています。名前付きパラメーターを使用すると、インターフェイスの作成者が適切なパラメーター名と関数名を指定できるため、そのインターフェイスを使用するユーザーが読み取り可能なコードを簡単に記述できます。
-Phoshi

@Phoshi-あなたは正しいです。以前のコメントを書いたとき、コードを読むことの重要性を忘れました。
mouviciel

14

名前付きパラメーターにより、コードが読みやすくなり、記述しにくくなります

コードを読んでいるとき、名前付きパラメーターは、コードを理解しやすくするコンテキストを導入できます。たとえば、次のコンストラクタを検討してください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; }});

3
" it is easier to forget the exact names of some parameters than it is to forget their order" ...それは興味深い主張です。
Catskul 14年

1
最初の反例は、名前付きパラメーターの問題ではなく、英語の複数形がインターフェースのフィールド名にどのようにマッピングされるかという問題です。2番目の反例も同様に、開発者が誤った命名の選択を行う問題です。(インターフェイスを渡すパラメーターの選択自体は、読みやすさの十分条件ではありません。問題は、それが必要条件であるか、場合によってはそれに役立つかどうかです)。
mtraceur

1
実装コストと、名前付きパラメーターを実行することとのトレードオフに関する全体的なポイントに対して+1。最初は人を失うと思う。
mtraceur

@mtraceurあなたにはポイントがあります。今、5年後、私はおそらくまったく違う答えを書くでしょう。必要に応じて、私の回答を編集してあまり役に立たないものを削除できます。(通常、そのような編集は著者の意図と矛盾するため拒否されますが、ここでは大丈夫です)。たとえば、多くの名前付きパラメータの問題は動的言語の制限にすぎず、型システムを取得するだけでよいと今は信じています。たとえば、JavaScriptにはFlowとTypeScriptがあり、PythonにはMyPyがあります。適切なIDE統合により、ほとんどのユーザビリティの問題が解消されます。私はまだPerlで何かを待っています。
アモン

7

同じ理由で、ハンガリー記法はもはや広く使用されていません。IntelliSense(またはMicrosoft IDE以外の場合のその道徳的同等物)。最新のIDEのほとんどは、パラメーター参照にカーソルを合わせるだけで、パラメーターについて知る必要があるすべての情報を提供します。

ただし、C#やDelphiなど、多くの言語が名前付きパラメーターをサポートしています。C#ではオプションであるため、必要ない場合は使用する必要はありません。また、オブジェクトの初期化など、メンバーを明示的に宣言する他の方法があります。

名前付きパラメーターは、すべてではなく、オプションのパラメーターのサブセットのみを指定する場合に役立ちます。C#では、プログラマにこの柔軟性を提供するために多数のオーバーロードメソッドが必要なくなるため、これは非常に便利です。


4
Pythonは名前付きパラメーターを使用しますが、これはこの言語のすばらしい機能です。より多くの言語がそれを使用することを望みます。C ++のように、デフォルトの「前」に引数を明示的に指定する必要がなくなるため、デフォルト引数が簡単になります。(あなたの最後の段落にあるように、それはPythonがオーバーロードなしで逃げる方法です...デフォルト引数と名前引数はすべて同じことをさせます。)名前付き引数はより読みやすいコードにもなります。のようなものがある場合sin(radians=2)、「IntelliSense」は必要ありません。
ロボット

2

Bashは確かにこのスタイルのプログラミングをサポートし、PerlとRubyの両方が名前/値パラメーターリストの受け渡しをサポートします(ネイティブハッシュ/マップサポートのある言語と同様)。パラメーターに名前を付ける構造体/レコードまたはハッシュ/マップを定義することを選択するのを妨げるものは何もありません。

ハッシュ/マップ/キーバリューストアをネイティブに含む言語の場合、キー/値ハッシュを関数/メソッドに渡すイディオムを採用するだけで、必要な機能を取得できます。プロジェクトで試してみると、使いやすさに起因する生産性の向上、または欠陥の減少による品質の向上のいずれかから何かを得たかどうかについて、より良い洞察が得られます。

経験豊富なプログラマは、注文/スロットごとにパラメータを渡すことに慣れていることに注意してください。また、このイディオムは、いくつかの引数(たとえば、5/6以上)がある場合にのみ価値があることもあります。また、多くの引数は多くの場合(過度に)複雑なメソッドを示しているため、このイディオムは最も複雑なメソッドに対してのみ有益であることがわかります。


2

これは、C#がVB.netよりも人気がある理由と同じだと思います。VB.NETは、「括弧」ではなく「end if」と入力すると「読みやすく」なりますが、最終的にはコードを詰め込んで理解するのが難しくなります。

コードを理解しやすくするのは簡潔さであることがわかります。少ないほど良い。とにかく、関数のパラメーター名は通常とにかく明らかであり、コードを理解するのにあまり役立つことはありません。


1
私は「少ないほど良い」ということに強く反対します。その論理的な極端に取られて、コードゴルフの勝者は「最高の」プログラムになるでしょう。
ライリーメジャー

2

名前付きパラメータは、ソースコードのリファクタリングにおける問題の解決策であり、ソースコードを読みやすくすることを目的としたものではありません。名前付きパラメーターは、関数呼び出しのデフォルト値を解決する際にコンパイラー/パーサーを支援するために使用されます。意味のあるコメントを追加するよりも、コードを読みやすくしたい場合。

リファクタリングが困難な言語は、名前の付いたパラメーターをサポートすることがよくあります。これfoo(1)は、署名がfoo()変更さfoo(name:1)れると破損する可能性があるためfooです。

次の関数に新しいパラメーターを導入する必要があり、その関数を呼び出すコードの行が数百または数千ある場合、何をしますか?

foo(int weight, int height, int age = 0);

ほとんどのプログラマは次のことを行います。

foo(int weight, int height, int age = 0, string gender = null);

現在、リファクタリングは不要であり、gendernullの場合、関数はレガシーモードで実行できます。

特定genderの値は、の呼び出しにハードコードされていますage。この例として:

 foo(10,10,0,"female");

プログラマーは関数定義を見ageて、デフォルト値が0あり、その値を使用していることを見ました。

今のデフォルト値はage関数定義では、完全に役に立たないです。

名前付きパラメーターを使用すると、この問題を回避できます。

foo(weight: 10, height: 10, gender: "female");

新しいパラメーターを追加することができ、追加されfooた順序について心配する必要はありません。また、設定したデフォルトが実際のデフォルト値であることがわかっているため、デフォルト値を変更できます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.