web-dev-qa-db-ja.com

構文設計-引数が渡されないときに括弧を使用する理由

多くの言語では、構文function_name(arg1, arg2, ...)を使用して関数を呼び出します。引数なしで関数を呼び出す場合は、function_name()を実行する必要があります。

関数呼び出しとして正常に検出するには、コンパイラまたはスクリプトインタープリターが_()_を必要とするのは奇妙です。変数が呼び出し可能であることがわかっている場合、なぜ_function_name;_では不十分なのでしょうか?

一方、一部の言語では、次のことができます。_function_name 'test';_または_function_name 'first' 'second';_で関数またはコマンドを呼び出す。

かっこは、優先順位を宣言するためだけに必要であり、他の場所ではオプションである場合に、より優れていたと思います。たとえば、_if expression == true function_name;_を実行すると、if (expression == true) function_name();と同じくらい有効になります。

私の意見で最も厄介なのは、プロトタイプ関数で引数が明らかに必要ないときに'SOME_STRING'.toLowerCase()を実行することです。なぜデザイナーはより単純な_'SOME_STRING'.lower_に反対したのですか?

免責事項:誤解しないでください。Cのような構文が大好きです! ;)私はそれがより良いかもしれないかどうか尋ねています。 _()_を必要とすることにはパフォーマンス上の利点がありますか、それともコードの理解が容易になりますか?私はその理由が何であるかについて本当に興味があります。

65
David Refoua

ファーストクラス関数 を使用する言語の場合、関数へのreferringの構文は次のようになります。

_a = object.functionName
_

一方、callingの動作は次のとおりです。

_b = object.functionName()
_

上記の例のaは、上記の関数への参照です(a()を実行して呼び出すこともできます)。一方、bには、関数の戻り値が含まれます。

一部の言語は括弧なしで関数呼び出しを実行できますが、関数がcallingであるか、単にであるかによって混乱する可能性があります)関数を参照

250
Nathan Merrill

確かに、Scalaはこれを許可しますが、慣例に従っています:メソッドに副作用がある場合は、とにかく括弧を使用する必要があります。

コンパイラー作成者として、括弧の存在が保証されていることは非常に便利だと思います。私は常にそれがメソッド呼び出しであることを知っていて、奇妙な場合のために分岐を組み込む必要はありません。

プログラマーおよびコードリーダーとして、かっこの存在は、パラメーターが渡されない場合でも、それがメソッド呼び出しであることを疑いません。

パラメータの受け渡しは、メソッド呼び出しの唯一の定義特性ではありません。パラメータのないメソッドを、パラメータのあるメソッドと異なる方法で処理するのはなぜですか?

63
Robert Harvey

これは、実際にはかなり微妙な構文の選択肢です。型付きラムダ計算に基づく関数型言語について説明します。

上記の言語では、すべての関数に厳密に1つの引数があります。私たちがしばしば「複数の引数」と考えるのは、実際には製品タイプの単一のパラメーターです。たとえば、2つの整数を比較する関数は次のとおりです。

_leq : int * int -> bool
leq (a, b) = a <= b
_

整数のペアを1つ取ります。括弧はnotが関数のパラメータを示します。それらはpattern-match引数に使用されます。これが実際に1つの引数であることを納得させるために、パターンマッチングの代わりに射影関数を使用してペアを分解できます。

_leq some_pair = some_pair.1 <= some_pair.2
_

したがって、かっこは、パターンマッチングや入力の手間を省くのに非常に便利です。それらは必要ありません。

見かけ上は引数がない関数はどうですか?そのような関数は実際にはドメインUnitを持っています。 Unitの単一のメンバーは通常_()_として記述されるため、括弧が表示されます。

_say_hi : Unit -> string
say_hi a = "Hi buddy!"
_

この関数を呼び出すには、Unit型の値に適用する必要があります。これは_()_である必要があるため、say_hi ()と記述します。

したがって、実際には引数リストのようなものはありません!

19
gardenhead

たとえばJavascriptでは、()なしでメソッド名を使用すると、実行せずに関数自体が返されます。このようにして、たとえば、関数を引数として別のメソッドに渡すことができます。

Javaでは、識別子の後に()または(...)が続く識別子がメソッド呼び出しを意味する一方で、()のない識別子がメンバー変数を参照するように選択されました。これにより、読みやすさが向上する可能性があります。メソッドとメンバー変数のどちらを扱っているかは間違いありませんが、メソッドとメンバー変数の両方に同じ名前を使用でき、どちらもそれぞれの構文でアクセスできます。

14
Florian F

構文はセマンティクスに従っているため、セマンティクスから始めましょう。

関数またはメソッドの使用方法は何ですか?

実際には、いくつかの方法があります。

  • 引数の有無にかかわらず、関数を呼び出すことができます
  • 関数は値として扱うことができます
  • 関数は部分的に適用できます(少なくとも1つ少ない引数を取るクロージャーを作成し、渡された引数を閉じる)

さまざまな用途で同様の構文を使用することは、あいまいな言語を作成するか、少なくとも混乱する言語を作成するための最良の方法です(そして十分な数の言語があります)。

CおよびCに似た言語:

  • 関数の呼び出しは、func()のように、かっこを使用して引数(存在しない場合もあります)を囲むことによって行われます。
  • 関数を値として扱うには、&funcのようにその名前を使用するだけで実行できます(Cは関数ポインターを作成します)。
  • 一部の言語では、部分的に適用するための簡単な構文があります。 Javaは例えばsomeVariable::someMethodを許可します(メソッドレシーバーに限定されますが、それでも有用です)

それぞれの使用法には異なる構文があり、簡単に区別できることに注意してください。

11
Matthieu M.

他の答えはどれも、問題に取り組むことを試みていません:言語の設計にどれくらいの冗長性があるべきですか?たとえx = sqrt yは、xをyの平方根に設定しますが、必ずしもそうする必要はありません。

冗長性のない言語では、すべての文字シーケンスは何かを意味します。つまり、1つのミスをすると、エラーメッセージが表示されず、プログラムは間違った動作をします。意図されたデバッグは非常に困難です。 (正規表現を使用したことのある人なら誰でも知っているでしょう。)冗長性が少ないと多くのエラーを検出できるので、冗長性が高ければ高いほど、診断が正確になる可能性が高くなります。もちろん、あなたはそれをあまりにも遠くに取ることができます(最近の誰もがCOBOLで記述したくない)が、正しいバランスがあります。

手がかりが多いため、冗長性は読みやすさにも役立ちます。冗長性のない英語は読みにくく、プログラミング言語にも同じことが言えます。

9
Michael Kay

副作用のある言語では、変数の読み取り(理論的には副作用なし)を区別することはIMOが非常に役立ちます

variable

副作用を引き起こす可能性のある関数の呼び出し

launch_nukes()

OTOH、副作用がない場合(型システムでエンコードされたもの以外)、変数の読み取りと引数なしの関数の呼び出しを区別する意味がありません。

7
Daniel Jour

ほとんどの場合、これらは言語の文法の構文上の選択です。さまざまな個々の構成要素の文法をまとめて(比較的)明確にすることは有用です。 (いくつかのC++宣言のようにあいまいな場合は、解決のための特定のルールが必要です。)コンパイラーは推測の自由度がありません。言語仕様に従う必要があります。


Visual Basicは、さまざまな形式で、値を返さないプロシージャと関数を区別します。

プロシージャはステートメントとして呼び出す必要があり、括弧は必要ありません。カンマで区切られた引数があれば、それだけです。関数は式の一部として呼び出す必要があり、括弧が必要です。

2つのフォーム間の手動リファクタリングを必要以上に面倒にするのは、比較的不必要な区別です。

(一方、Visual Basicは、配列参照に関数呼び出しと同じ括弧()を使用するため、配列参照は関数呼び出しのように見えます。これにより、配列を関数呼び出しに手動でリファクタリングすることが簡単になります!したがって、熟考することができます。他の言語は配列参照に[]を使用しますが、割愛します...)


CおよびC++言語では、変数の内容はその名前を使用して自動的にアクセスされます。内容ではなく変数自体を参照する場合は、単項&演算子を適用します。

この種類のメカニズムは、関数名にも適用できます。生の関数名は関数呼び出しを意味する可能性がありますが、単項&演算子は、関数(それ自体)をデータとして参照するために使用されます。個人的には、変数と同じ構文で副作用のない引数のない関数にアクセスするアイデアが好きです。

これは完全にもっともらしいです(このための他の構文上の選択もそうです)。

5
Erik Eidt

一貫性と読みやすさ。

関数XX(arg1, arg2, ...)のように呼び出すことがわかった場合、引数なしでも同じように機能するはずです:X()

同時に、変数をシンボルとして定義し、次のように使用できることがわかります。

a = 5
b = a
...

これを見つけたとき、私はどう思いますか?

c = X

あなたの推測は私のものと同じです。通常の状況では、Xは変数です。しかし、もし私たちがあなたの道をたどるならば、それはまた機能であるかもしれません!どのシンボルがどのグループ(変数/関数)にマッピングされるかを覚えておくべきですか?

たとえば、人為的な制約を課すことができます。 「関数は大文字で始まります。変数は小文字で始まります」しかし、それは不必要であり、物事をより複雑にしますが、設計目標の一部に役立つ場合があります。

サイドノート1:他の答えはもう1つ完全に無視します。言語では、関数と変数の両方に同じ名前を使用でき、コンテキストで区別できる場合があります。例としてCommon LISPを参照してください。関数xと変数xは完全にうまく共存します。

サイドノート2:受け入れられた回答は構文object.functionnameを示しています。第一に、これはファーストクラスの機能を持つ言語に共通ではありません。次に、プログラマーとしてこれを追加情報として扱います。functionnameobjectに属します。 objectがオブジェクトであるかどうかにかかわらず、クラスまたは名前空間はそれほど重要ではありませんが、それが属しているどこかであることがわかります。つまり、グローバル関数ごとに人為的な構文object.を追加するか、すべてのグローバル関数を保持するobjectを作成する必要があります。

そしてどちらにしても、関数と変数に別々の名前空間を持つ能力を失います。

5
MatthewRock

他の回答に追加するには、次のCの例を見てください。

void *func_factory(void)
{
        return 0;
}

void *(*ff)(void);

void example()
{
        ff = func_factory;
        ff = func_factory();
}

呼び出し演算子がオプションの場合、関数の割り当てと関数の呼び出しを区別する方法はありません。

これは、型システムが不足している言語ではさらに問題になります。 JavaScript。型の推論を使用して、関数とそうでないものを判別することはできません。

4
Mark K Cowan