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


66

多くの言語では、構文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のような構文が大好きです!;)私はそれがもっと良いかどうか尋ねているだけです。要求に()はパフォーマンス上の利点がありますか、それともコードを理解しやすくしますか?正確な理由は何なのか本当に興味があります。


103
関数を別の関数の引数として渡したい場合はどうしますか?
ビンセントサバード

30
コードを人間が読む必要がある限り、読みやすさが重要です。
Tulainsコルドバ

75
それは読みやすさです。あなたはについて尋ね()ますが、あなたの投稿で際立っているのはif (expression == true)声明です。あなたは余分なものを心配し()、それから余分なものを使用します== true:)
デビッドアルノ

15
Visual Basicはこれを許可しました
-edc65

14
Pascalでは必須であり、引数を取る関数とプロシージャにのみ括弧を使用しました。
-RemcoGerlich

回答:


251

ファーストクラスの関数を使用する言語の場合、関数を参照する構文は次のとおりであることが非常に一般的です。

a = object.functionName

その関数を呼び出す行為は次のとおりです。

b = object.functionName()

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

一部の言語は括弧なしで関数呼び出しを行うことができますが、それは彼らがしているかどうか混乱得ることができます呼び出し機能を、または単に参照関数に。


9
この違いは副作用の存在下でのみ興味深いことを言及したいかもしれません-純粋な機能にとっては重要ではありません
-Bergi

73
@Bergi:純粋な関数にとって重要です!make_list引数をリストにパックする関数がある場合f(make_list)、関数をf渡すか空のリストを渡すかには大きな違いがあります。
user2357112

12
@Bergi:それでも、SATInstance.solveすぐに実行しようとせずに、他の信じられないほど高価な純粋な関数を別の関数に渡したいと思っています。また、純粋であると宣言さsomeFunctionれているかどうかに応じて何か異なることをすると、事態は本当に厄介someFunctionになります。私は(擬似コード)を持っている場合たとえば、func f() {return g}func g() {doSomethingImpure}、そしてfunc apply(x) {x()}、その後、apply(f)呼び出しますf。その後f、それが純粋であると宣言すると、突然にapply(f)渡さgapply、不純なものをapply呼び出しgて実行します。
user2357112

6
@ user2357112評価戦略は純度とは無関係です。呼び出しの構文を注釈として使用して、可能なことを評価する場合(ただし、おそらく厄介です)、ただし、結果またはその正確性は変更されません。あなたの例に関してapply(f)、もしg不純である(そしてapply同様に?)場合、全体が不純であり、もちろん故障します。
ベルギ

14
この例としては、PythonとECMAScriptがあります。どちらも「メソッド」というコアコンセプトを実際に持っておらず、代わりに匿名のファーストクラス関数オブジェクトを返す名前付きプロパティがあり、その匿名のファーストクラス関数を呼び出します。具体的に言って、PythonとECMAScriptの両方でfoo.bar()はない1操作「呼び出しメソッドbarオブジェクトのfoo」むしろ2つの動作、「間接参照フィールドbarオブジェクトのfoo その後やオブジェクトを呼び出し、そのフィールドから返されました」。したがって、.フィールドセレクタ演算子()あり、呼び出し演算子であり、独立しています。
ヨルグWミットタグ

63

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

コンパイラライターとして、括弧の存在を保証することは非常に便利です。私はそれがメソッド呼び出しであることを常に知っているだろうし、奇妙なケースのために分岐を構築する必要はないだろう。

プログラマーおよびコードリーダーとして、括弧が存在することは、パラメーターが渡されない場合でも、メソッド呼び出しであることは間違いありません。

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


ありがとう、そしてこれがなぜ重要なのかについて正当な理由を述べました。また、言語設計者がプロトタイプで関数を使用する必要がある理由について、いくつか例を挙げてください。
デビッドRefoua

括弧が存在することは、それが完全に同意するメソッド呼び出しであることは間違いありません。私はプログラミング言語を暗黙よりも明示的にすることを好みます。
コーリーオグバーン

20
実際、Scalaはそれよりも少し微妙です。他の言語では、ゼロ個以上のパラメーターを持つパラメーターリストを1つしか使用できませんが、Scalaでは、ゼロ個以上のパラメーターを持つパラメーターリストを使用できます。だから、def foo()1つの空のパラメータリストをdef foo持つメソッドであり、パラメータリストがないメソッドであり、2つは異なるものです!一般的に、パラメータリストのないメソッドは引数リストなしで呼び出す必要があり、パラメータリストのないメソッドは引数リストが空の状態で呼び出す必要があります。空の引数を指定していないパラメータリストを持つメソッドを呼び出すと...実際にある
イェルクWミッターク

19
… 空の引数リストを使用してメソッド呼び出しによって返さapplyたオブジェクトのメソッドを呼び出すと解釈されますが、引数リストなしで空のパラメーターリストを使用してメソッドを呼び出すと、コンテキストによっては部分的に適用されたメソッド(つまり、「メソッドリファレンス」)へのη拡張、またはまったくコンパイルされない可能性があります。また、Scalaは引数リストなしでメソッドを呼び出すこととフィールドを参照することを(構文的に)区別しないことで、Uniform Access Principleに従います。
ヨルグWミットタグ

3
必要な「式」(括弧、中括弧、明示的な型)がないことでRubyに感謝するようになったとしても、method-かっこがより速く読めることはわかります。しかし、かつて馴染みのある慣用的なRubyは非常に読みやすく、間違いなく高速に記述できます。
レーダーボブ

19

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

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

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

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

leq some_pair = some_pair.1 <= some_pair.2

したがって、カッコは実際にパターンマッチングと入力の節約を可能にする便利なものです。必須ではありません。

表向きは引数を持たない関数はどうですか?このような関数には実際にはdomainがありますUnit。の単一のメンバーUnitは通常として記述され()ているため、括弧が表示されます。

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

この関数を呼び出すには、我々は、型の値に適用しなければならないUnitでなければならない、()ので、私たちは書き込みを終わりますsay_hi ()

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


3
そして、それが空の括弧()がスプーンからハンドルを引いたものに似ている理由です。
マインドウィン

3
定義leq使用する関数<ではなく<=、非常に紛らわしいです!
KRyan

5
この答えは、「製品タイプ」などのフレーズに加えて「タプル」という単語を使用すると理解しやすくなります。
ケビン

ほとんどの形式はint f(int x、int y)をI ^ 2からIへの関数として扱います。ラムダ計算は異なり、このメソッドを使用して他の形式の高アリティと一致することはありません。代わりに、ラムダ計算ではカリー化が使用されます。比較はx->(y->(x <= y))になります。(x->(y->(x <= y))2は(y-> 2 <= y)に評価され、(x->(y->(x <= y))2 3は( y-> 2 <= y)3から2 <= 3と評価される
Taemyr

1
@PeriataBreatta ()ユニットを表すために使用した歴史に詳しくありません。少なくともその理由の一部が「パラメータのない関数」に似ていることであれば、私は驚かないでしょう。ただし、構文には別の可能な説明があります。型の代数でUnitは、実際には製品型のアイデンティティです。バイナリ積はとして書かれてい(a,b)ますが、単項積を書くことができる(a)ので、論理的拡張は単純にヌル積を書くことです()
ガーデンヘッド

14

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

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


4
+1人間がコードを読む必要がある限り、可読性が重要です。
Tulainsコルドバ

1
@TulainsCórdovaperlがあなたに同意するかどうかわかりません。
ラチェット

@Racheet:Perlはそうでないかもしれませんが、Perl Best Practicesそうです
slebetman

1
@Racheet Perlは、読みやすいように記述されている場合、非常に読みやすくなります。ほとんどすべての非難解な言語で同じです。短く簡潔なPerlは、ScalaまたはHaskellのコードがこれらの言語を経験した人々にとって読みやすいように、Perlプログラマにとって非常に読みやすいです。また、文法的に完全に正しい非常に複雑な方法でドイツ語を話すこともできますが、ドイツ語に堪能であっても単語を理解することはできません。それはドイツ人のせいでもありません。;)
シンバク

Javaでは、メソッドを「識別」しません。それを呼び出します。Object::toStringメソッドを識別します。
njzk2

10

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

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

実際には、複数の方法があります。

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

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

CおよびCライクな言語の場合:

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

各使用法が異なる構文を備えており、簡単に区別できることに注意してください。


2
「簡単に」というのは、「既に理解されている場合にのみ明らか」な状況の1つです。初心者は、なぜ句読点に意味があるのか​​を説明せずに決して明白ではないことに気付くと完全に正当化されるでしょう。
エリックリッパー

2
@EricLippert:実際、簡単に言うと必ずしも直感的というわけではありません。この場合、括弧は数学の関数「呼び出し」にも使用されることを指摘します。そうは言っても、多くの言語の初心者は、一般的な構文よりも概念/意味論に苦労しています。
マチューM.

この答えは、「カリー化」と「部分適用」の区別を混同します。::Java の演算子は、カリー化演算子ではなく、部分的なアプリケーション演算子です。部分適用は、関数と1つ以上の値を取り、より少ない値を受け入れる関数を返す操作です。カリー化は、値ではなく関数でのみ動作します。
ペリアタブレアッタ

@PeriataBreatta:良い点、修正。
マチューM.

8

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

variable

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

launch_nukes()

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


1
OPは、オブジェクトが唯一の引数であり、関数名(関数名のスコープも提供する)の前に表示される場合を示していることに注意してください。noun.verb構文は、意味が同じくらい明確でnoun.verb()、少し混乱が少ないかもしれません。同様に、member_左参照を返す関数は、典型的なセッター関数よりもわかりやすい構文を提供します:obj.member_ = new_valuevs. obj.set_member(new_value)_接尾_ 辞は、「隠された」関数の接頭辞を連想させる「露出した」というヒントです)。
ポールA.クレイトン

1
@ PaulA.Claytonこれが私の答えでどのように扱われていないのかわかりません:noun.verb関数呼び出しを意味する場合、単なるメンバーアクセスとどのように区別しますか?
ダニエルジュール

1
理論的に副作用のない変数の読み取りに関しては、これを言いたいだけです。常に偽の友人に注意してください
ジャスティンタイム

8

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

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

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


ある意味では、プログラミング言語には「セーフティネット」メカニズムが必要です。しかし、構文の「少しの冗長性」がそれを回避する良い方法であることに同意しません-それは定型的なものであり、それが主に行うことはエラーを見つけにくくするノイズを導入し、構文エラーの可能性を広げることです本当のバグ。読みやすさを助長する種類の冗長性は、構文とはほとんど関係がなく、命名規則と関係があります。また、セーフティネットは、強力な型システムとしてはるかに効率的に実装されており、ほとんどのランダムな変更により、プログラムの型が不適切になります。
左辺約

@leftaroundabout:ハミング距離の観点から言語設計の決定について考えるのが好きです。2つのコンストラクトの意味が異なる場合、少なくとも2つの方法が異なり、1つの方法のみが異なる形式を使用してコンパイラスコークを生成すると便利な場合があります。言語設計者は、一般的に、識別子は容易に区別されていることを保証する責任を負うことはできませんが、例えば割り当てが必要とする場合:=と比較==して、=唯一のコンパイル時の初期化のために使用可能なことは、その後の割り当てに比較を回すタイプミスの可能性が大幅に減少することでしょう。
-supercat

@leftaroundabout:同様に、言語を設計して()いる場合、引数のない関数の呼び出しが必要になる可能性がありますが、関数の「アドレスを取得」するトークン必要になります。前者のアクションははるかに一般的であるため、あまり一般的でないケースでトークンを保存することはそれほど有用ではありません。
-supercat

@supercat:さて、これは間違ったレベルで問題を解決していると思います。割り当てと比較は概念的に異なる操作であるため、関数の評価と関数のアドレスの取得もそれぞれ異なります。したがって、それらは明確に異なる型を持つ必要があり、演算子のハミング距離が低い場合、タイプミスはコンパイル時の型エラーになるため、実際にはもう問題ではありません。
左辺り

@leftaroundabout:ブール型変数に割り当てが行われている場合、または関数の戻り値の型自体が関数へのポインターである場合(または一部の言語では実際の関数)、比較を割り当てに置き換え、または関数参照を使用した関数呼び出しにより、構文的には有効ですが、元のバージョンとはまったく異なる意味を持つプログラムが生成される場合があります。一般に、型チェックはそのようなエラーをいくつか見つけますが、構文を明確にすることはより良いアプローチのようです。
supercat

5

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


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

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

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

(一方のVisual Basicは同じ括弧使用する()関数呼び出し用として、配列参照のために、その関数が呼び出されます。そして、これは、関数呼び出しへの配列の手動リファクタリングを容易にします!だから、私たちは他の言語を使う熟考可能性のような配列の参照が見える[]の配列参照のためですが、私は脱線します...)


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

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

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


2
もちろん、Visual Basicのプロシージャコールに対するブラケットの欠如は、その関数コールと比較して不必要な区別ですが、必要なブラケットを追加することはステートメント間の不必要な区別になります。BASIC派生言語の多くは非常に効果的です手順を連想させる。MS BASICバージョンで作業を行ってからしばらく経ちましたが、まだ構文を許可していませんcall <procedure name> (<arguments>)か?かなり確信しているが...私は別の一生の間にQuickBASICのプログラミングをした際にバック手順を使用しての有効な方法だった
Periata Breatta

1
@PeriataBreatta:VBはまだ「呼び出し」構文を許可し、呼び出されるものが式である場合に必要になる場合があります[たとえば、「Call If(someCondition、Delegate1、Delegate2)(Arguments)」と言うことができますが、 「If」演算子の結果の呼び出しは、スタンドアロンステートメントとしては無効です。
supercat

@PeriataBreatta VB6のブラケットプロシージャコールは、DSLのようなコードの見栄えを良くしたので気に入っています。
マークハード

@supercat LinqPadではCall、「通常の」プロダクションコードではほとんど必要のないメソッドにどれほど頻繁に使用する必要があるかは驚くべきことです。
マークハード

5

一貫性と読みやすさ。

Xこのように関数を呼び出すことを知った場合X(arg1, arg2, ...)、引数がない場合でも同じように機能することを期待しています:X()

同時に、変数をシンボルとして定義し、次のように使用できることを学びました。

a = 5
b = a
...

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

c = X

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

「関数は大文字で始まる。変数は小文字で始まる」などの人為的な制約を課すこともできますが、これは不要であり、設計目標の一部に役立つことがありますが、より複雑になります。

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

サイドノート2:受け入れられた答えは、構文を示していますobject.functionname。第一に、それは一流の関数を持つ言語に普遍的ではありません。第二に、プログラマとして、私はこれを追加情報として扱います:にfunctionname属しobjectます。objectオブジェクトであるか、クラスであるか、名前空間であるかはそれほど重要ではありませんが、どこかに属していることを教えてくれます。つまりobject.、各グローバル関数に人工的な構文を追加するかobject、すべてのグローバル関数を保持するためにいくつかの構文を作成する必要があります。

どちらにしても、関数と変数に別々の名前空間を持たせることができなくなります。


うん。一貫性は十分な理由であり、完全に停止しています(他のポイントが有効ではないということではなく、有効です)。
マシュー

4

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

void *func_factory(void)
{
        return 0;
}

void *(*ff)(void);

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

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

これは、JavaScriptなどの型システムを持たない言語ではさらに問題になります。JavaScriptでは、型推論を使用して関数の有無を判断することはできません。


3
JavaScriptには型システムが欠けていません。型宣言がありません。しかし、異なるタイプの価値の違いを完全に認識しています。
ペリアタブレアッタ

1
Periata Breatta:ポイントは、Javaの変数とプロパティに任意の値の型を含めることができるため、コード読み取り時に関数であるかどうかを常に把握できるとは限らないことです。
reinierpost

たとえば、この関数は何をしますか?function cross(a, b) { return a + b; }
マークKコーワン

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