ユーザー定義の演算子が一般的ではないのはなぜですか?


94

関数型言語で見落としている機能の1つは、演算子が単なる関数であるという考えです。したがって、カスタム演算子を追加することは、関数を追加するのと同じくらい簡単です。多くの手続き言語では演算子のオーバーロードが許可されているため、ある意味では演算子は関数のままです(これは、テンプレートパラメーターの文字列として演算子が渡されるDで非常に当てはまります)。

演算子のオーバーロードが許可されている場合、カスタム演算子を追加するのは簡単なことが多いようです。私はこのブログ投稿を見つけました。これは、カスタム演算子が優先順位規則のために中置記法でうまく機能しないと主張していますが、著者はこの問題にいくつかの解決策を提供します。

私は周りを見回しましたが、その言語のカスタム演算子をサポートする手続き言語を見つけることができませんでした。ハック(C ++のマクロなど)がありますが、それは言語サポートとほとんど同じではありません。

この機能の実装は非常に簡単なので、なぜもっと一般的ではないのですか?

itいコードにつながる可能性があることは理解していますが、それによって、過去に言語設計者が簡単に悪用される可能性のある便利な機能(マクロ、三項演算子、安全でないポインター)を追加することを妨げませんでした。

実際の使用例:

  • 欠落している演算子を実装します(たとえば、Luaにはビット演算子がありません)
  • Dの模倣~(配列連結)
  • DSL
  • |Unixパイプスタイルの構文シュガーとして使用(コルーチン/ジェネレーターを使用)

私はまた、言語に興味があるカスタム演算子を許可するが、私は、より興味なぜそれが除外されています。スクリプト言語をフォークしてユーザー定義の演算子を追加することを考えましたが、どこにも表示されていないことに気付いたときに自分自身を止めました。したがって、言語デザイナーが私よりも賢いのは私がそれを許可していないことにはおそらく十分な理由があります。


4
この質問についてはRedditの議論が続いています。
ダイナミック

2
@dimatura:Rについてはあまり知りませんが、カスタム演算子はまだあまり柔軟ではないようです(たとえば、固定性、スコープ、オーバーロードなどを定義する適切な方法がありません)。これは他の言語では異なりますが、カスタム挿入演算子を非常に多く使用するHaskellでは確かです。合理的にカスタムインフィックスをサポートする手続き型言語の別の例はNimrodであり、もちろんPerlもそれらを許可しています
leftaroundabout

4
他のオプションがあります。Lispには実際には演算子と関数の間に違いはありません。
BeardedO

4
Prologはまだ言及されていません。これは、演算子が関数の単なる構文上の砂糖(数学関数であっても)でありカスタム優先順位でカスタム演算子を定義できる別の言語です。
デビッドカウデン

2
@ BeardedO、Lispには中置演算子はまったくありません。それらを導入したら、すべての問題を優先的に処理する必要があります。
SKロジック

回答:


134

プログラミング言語の設計には、正反対の2つの考え方があります。1つは、プログラマがより少ない制限でより良いコードを書くことであり、もう1つはより多くの制限でより良いコードを書くことです。私の意見では、現実には優れた経験豊富なプログラマーはより少ない制限で繁栄しますが、その制限は初心者のコード品質に利益をもたらす可能性があります。

ユーザー定義の演算子は、経験豊富な手で非常にエレガントなコードを作成でき、初心者でもまったくひどいコードを作成できます。したがって、あなたの言語がそれらを含むかどうかは、あなたの言語設計者の考え方に依存します。


23
plus.google.com/110981030061712822816/posts/KaSKeg4vQtzの2つの考え方では言語デザイナーが一方を選択する理由と他方を選択する理由の質問に対して最も直接的な(そしておそらく最も真実の)答えを持っていることに対しても+1です。また、あなたの論文に基づいて、より少ない言語の開発者が非常に熟練しているため、より少ない言語がそれを許可すると推定できると言います(定義上、すべての上位パーセントは常に少数派です)
ジミーホファ

12
この答えはあいまいで、質問にわずかに関連しているだけだと思います。(私の経験と矛盾するので、あなたの特性評価は間違っていると思いますが、それは重要ではありません。)
コンラッドルドルフ

1
@KonradRudolphが言ったように、これは本当に質問に答えません。Prologのような言語を使用すると、中置記号または接頭辞を配置するときの優先順位を定義するなど、演算子で必要なことを何でも行うことができます。カスタムオペレータを作成できるという事実は、対象読者のスキルレベルとは関係ないと思いますが、Prologの目標は可能な限り論理的に読み取ることです。カスタム演算子を含めることにより、非常に論理的に読み取るプログラムを作成できます(すべてのプロローグプログラムは単なる論理ステートメントの集まりです)。
デビッドカウデン

そのスティーブの暴言を見逃したのはどうしてですか?ああ男、ブックマーク
ジョージ・マウアー

その哲学に私は、言語を言うためにそれらのツールを与えた場合、プログラマは最高のコードを書く可能性が最も高いと思われる場合、私は落ちるとき(に例えば、オートボクシングの引数をコンパイラは、さまざまな推論を行う必要がありますFormat(法)、それは拒否しなければならないとき例:の自動ボクシング引数ReferenceEquals)。特定の推論が不適切である場合に、プログラマーがより能力の高い言語で表現できるほど、適切な場合に便利な推論をより安全に提供できます。
supercat 14

83

〜で配列を連結するか、「myArray.Concat(secondArray)」で配列を選択するかを考えると、おそらく後者を好むでしょう。どうして?〜は完全に意味のない文字であるため、その意味は-配列連結の意味のみ-それが書かれた特定のプロジェクトで与えられます。

基本的に、あなたが言ったように、演算子はメソッドと違いはありません。しかし、メソッドには、コードフローの理解に役立つ読みやすいわかりやすい名前を付けることができますが、演算子は不透明で状況に応じたものです。

PHPの.演算子(文字列の連結)やHaskellやOCamlの演算子の大部分も好きではないのは、このためです。


27
同じことがすべてのオペレーターに言えるでしょう。それらを優れたものにし、ユーザー定義の演算子を優れたものにすることができます(賢明に定義されている場合)。それに精通している人には明らかです。したがって、現状のあなたの答えは、私見を納得させるものではありません。

33
最後に述べたように、一部の演算子は普遍的な認識可能性(標準的な算術記号、ビット単位のシフト、AND / ORなど)を持っているため、その簡潔さが不透明度を上回ります。しかし、任意の演算子を定義できることは、両方の世界の最悪の状態を維持するようです。
アヴナーシャハルカシュタン

11
それで、例えば、言語レベルで一文字の名前を禁止することに賛成ですか?より一般的には、言語の中で、善よりも害を及ぼすと思われるものを許可しないのですか?それは言語設計への有効なアプローチですが、それだけではないので、あなたはそれを前提にできないとは思いません。

9
カスタムオペレータが機能を「追加」していることには同意しません。制限を解除しています。通常、演算子は単なる関数であるため、演算子の静的シンボルテーブルの代わりに、動的なコンテキスト依存のテーブルを代わりに使用できます。確かに定義されていないため+、これが演算子のオーバーロードの処理方法だと思います(C ++の裸のクラスでそれを行うと「演算子+に一致しません...」を取得します)。<<Object
beatgammit

10
長い時間がかかるのは、コーディングとは通常、コンピューターに何かをさせることではなく、人とコンピューターの両方が読めるドキュメントを生成することであり、人はコンピューターよりもかなり重要だということです。この答えは正確です。次の人(または2年以内に)が〜の意味を理解しようとして10秒も費やす必要がある場合は、代わりにメソッド呼び出しの入力に10秒を費やした可能性があります。
ビルK

71

この機能の実装は非常に簡単なので、なぜもっと一般的ではないのですか?

あなたの前提は間違っています。「実装するのかなり簡単」ではありません。実際、それは多くの問題をもたらします。

投稿で提案されている「解決策」を見てみましょう。

  • 優先順位なし。著者自身は、「優先ルールを使用しないことは単に選択肢ではない」と述べています。
  • セマンティックアウェア解析。記事が言っているように、これにはコンパイラーに多くのセマンティック知識が必要です。この記事では実際にこれに対する解決策を提供しているわけではありません。これは簡単なことではありません。コンパイラは、パワーと複雑さのトレードオフとして設計されています。特に、著者は関連情報を収集するための事前解析ステップに言及していますが、事前解析は非効率的であり、コンパイラは解析パスを最小限に抑えるように非常に努力しています。
  • カスタム中置演算子はありません。まあ、それは解決策ではありません。
  • ハイブリッドソリューション。このソリューションには、セマンティックアウェア解析の欠点の多く(すべてではない)があります。特に、コンパイラは未知のトークンを潜在的にカスタムオペレータを表すものとして扱わなければならないため、意味のあるエラーメッセージを生成できないことがよくあります。また、解析を続行する(型情報などを収集する)ために、上記の演算子の定義が必要になる場合があり、もう一度追加の解析パスが必要になります。

全体として、これは、パーサーの複雑さとパフォーマンスの両方の点で、実装するのに高価な機能であり、多くの利点をもたらすかどうかは明らかではありません。確かに、新しい演算子を定義する能力にはいくつかの利点がありますが、それらは論争的です(新しい演算子を持つことは良いことではないと主張する他の答えを見てください)。


2
正気の声をありがとう。私の経験では、ようなものを退ける人々ことを示唆している些細は X:専門家や穏やかに無知、そしてより多くの場合、後者のカテゴリのいずれかである
マシューM.

14
20年も前から存在している言語で解決されているこれらの問題のすべての単一の1 ...
フィリップ・JF

11
それでも、実装するのは非常に簡単です。ドラゴンブックの解析アルゴリズムはすべて忘れてください。21世紀であり、先に進むべき時です。言語構文自体を拡張することさえ簡単であり、特定の優先順位の演算子を追加することは簡単です。Haskellパーサーを見てください。「主流の」肥大化言語のパーサーよりもはるかに単純です。
SKロジック

3
@ SK-logicあなたの全面的な主張は私を納得させません。はい、解析は進んでいます。いいえ、タイプに応じて任意の演算子の優先順位を実装することは「簡単」ではありません。限られたコンテキストで適切なエラーメッセージを生成することは「些細なこと」ではありません。変換に複数のパスを必要とする言語を使用して効率的なパーサーを作成することはできません。
コンラッドルドルフ

6
Haskellでは、解析は「簡単」です(ほとんどの言語と比較して)。優先順位はコンテキストに依存しません。ハードエラーメッセージを提供する部分は、ユーザー定義の演算子ではなく、タイプクラスと高度なタイプシステム機能に関連しています。難しい問題は、ユーザー定義ではなくオーバーロードです。
フィリップJF

25

とりあえず、「読みやすさを損なうために演算子が乱用される」という議論全体を無視し、言語設計の意味に焦点を当てましょう。

中置演算子には、単純な優先順位ルールよりも多くの問題があります(ただし、簡潔ではありますが、参照するリンクはその設計決定の影響を単純化します)。1つは競合の解決です:a.operator+(b)とを定義するとb.operator+(a)どうなりますか?一方を他方より優先すると、その演算子の予想される可換特性が壊れます。エラーを投げると、そうでなければ機能するモジュールが一度に壊れてしまう可能性があります。派生型をミックスにスローし始めるとどうなりますか?

問題の事実は、演算子は単なる関数ではないということです。関数は、スタンドアロンであるか、クラスによって所有されています。これにより、ポリモーフィックディスパッチを所有するパラメーター(存在する場合)を明確に設定できます。

そして、オペレーターから生じるさまざまなパッケージングと解決の問題を無視します。言語設計者が(全般的に)中置演算子の定義を制限する理由は、議論の余地のある利点を提供しながら、言語の問題の山を作成するためです。

率直に言って、実装するの簡単ではないからです。


1
これがJavaがそれらを含まない理由だと思いますが、それらを持っている言語には明確な優先ルールがあります。マトリックス乗算に似ていると思います。可換ではないので、行列を使用するときは注意する必要があります。クラスを扱うときに同じことが当てはまると思いますが、はい、非可換性を持つこと+は悪であることに同意します。しかし、これは本当にユーザー定義の演算子に対する議論ですか?一般的に、演算子のオーバーロードに対する議論のようです。
beatgammit

1
@tjameson-はい、言語には数学の優先順位に関する明確なルールがあります。演算子がのようなものでオーバーロードされている場合、数学演算の優先規則はおそらく実際には適用されませんboost::spirit。ユーザー定義の演算子を許可するとすぐに、数学の優先順位を適切に定義する良い方法がないため、悪化します。私は、自分自身について、arbitrarily意的に定義された演算子の問題に対処することを特に検討している言語のコンテキストで少し書いてます。
テラスティン

22
私はでたらめを呼び出します、先生。OOゲットーの外には生命があり、関数は必ずしもどのオブジェクトに属さないため、正しい関数を見つけることは正しい演算子を見つけることとまったく同じです。
マチューM.

1
@matthieuM - もちろん。メンバー関数をサポートしない言語では、所有権とディスパッチルールがより統一されます。しかし、ある時点で、元の質問は「なぜOO以外の言語がより一般的ではないのですか?」これはまったく他の球技です。
Telastyn

11
Haskellがカスタムオペレータをどのように扱っているか見てきましたか?これらは、関連する優先順位も持っていることを除いて、通常の機能とまったく同じように機能します。(実際、通常の関数も同様であるため、実際には違いはありません。)基本的に、演算子はデフォルトで中置であり、名前は接頭辞ですが、それが唯一の違いです。
Tikhon Jelvis

19

演算子のオーバーロード何らかの形で実装される頻度に驚かれると思います。しかし、多くのコミュニティでは一般的に使用されていません。

〜を使用して配列に連結するのはなぜですか?Rubyのように<<を使用しないのなぜですか?あなたが働くプログラマーはおそらくRubyプログラマーではないからです。またはDプログラマー。それで、あなたのコードに出くわしたとき、彼らは何をしますか?彼らは行って、シンボルが意味するものを調べなければなりません。

私はかつて関数型言語の好みを持っていた非常に優れたC#開発者と仕事をしていました。突然、彼は拡張メソッドと標準のモナド用語を使用してモナドをC#に導入し始めました。コードの意味がわかれば、彼のコードの一部がより簡潔で読みやすくなることに誰も異議を唱えることはできませんでしたが、それはコードが意味を成す前に誰もがモナド用語を学ばなければならないことを意味していました

結構ですよね?ほんの小さなチームでした。個人的には同意しません。すべての新しい開発者は、この用語で混乱する運命にありました。新しいドメインの学習に十分な問題はありませんか?

一方、私は喜んで使用する演算子を私は他のC#の開発者が期待するので、それが何であるかを知るためにC#で、私は、デフォルトではそれをサポートしていませんでした言語にそれが過負荷にならないでしょう。??


Double.NaNの例がわかりません。この動作は浮動小数点仕様の一部であり、使用したすべての言語でサポートされています。この機能について知らない開発者を混乱させるため、カスタム演算子はサポートされていないと言っていますか?これは、三項演算子または??例を使用することに対する同じ議論のように聞こえます。
beatgammit

@tjameson:確かにそうです、それは私がやろうとしていたポイントに少し接線方向であり、特にうまく書かれていませんでした。私は考えましたか?私がそれを書いていたときの例で、その例を好む。double.NaN段落を削除します。
pdr

6
あなたの「すべての新しい開発者はこの用語で混乱する運命にある」という点は少し不満だと思います。あなたのコードベースに対する新しい開発者の学習曲線を心配することは、私にとって.NETの新しいバージョン。もっと重要なのは、開発者がそれを学んだとき(新しい.NETまたはコードベース)、それが意味をなし、価値を示しているかどうかです。その場合、各開発者が一度だけ学習すればよいので、学習曲線は価値があります。ただし、コードは何度も処理する必要があります。
ジミーホファ

7
@JimmyHoffa繰り返し発生する費用です。すべての新規開発者に対して前払いし、既存の開発者もサポートする必要があるため、既存の開発者にも影響を与えます。ドキュメントのコストとリスクの要素もあります-今日は十分に安全だと感じていますが、30年後には誰もが移動し、言語とアプリケーションは「レガシー」になり、ドキュメントは巨大な山になりますそして、「。concat()」を入力するのが面倒だったプログラマーの才能に髪を引き裂く貧しい吸盤が残されます。期待値はコストを相殺するのに十分ですか?
Sylverdrag

3
また、「すべての新しい開発者はこの用語で混乱する運命にあります。新しいドメインを学習するのに十分な問題はありませんか?」という議論もあります。80年代のOOP、その前の構造化プログラミング、または10年前のIoCに適用できたかもしれません。この誤った議論の使用をやめる時が来ました。
マウリシオシェファー14年

11

いくつかの理由が考えられます。

  • それらを実装するのは簡単でありません -任意のカスタム演算子を許可すると、特にユーザー定義の優先順位、固定性、およびアリティ規則を許可する場合、コンパイラがより複雑になる可能性があります。シンプルさが美徳である場合、演算子のオーバーロードは優れた言語設計からあなたを引き離します。
  • それらは悪用されます -主にオペレーターを再定義し、あらゆる種類のカスタムクラスに対して再定義を開始するのが「クール」だと考えるコーダーによって。やがて、コードにはカスタマイズされたシンボルが大量に散らばり、オペレータは従来のよく理解されているルールに従わないため、他の誰も読み取ったり理解したりできなくなります。あなたのDSLがたまたま数学のサブセットである場合を除き、「DSL」引数を購入しません。
  • 彼らは、可読性と保守性を傷つける -演算子は定期的に上書きされている場合、それはこの機能が使用されている、とコーダーを継続的にオペレータが何をしているのかを自問することを余儀なくされたときに発見することは困難になることができます。意味のある関数名を指定することをお勧めします。いくつかの余分な文字を入力するのは安く、長期的なメンテナンスの問題は高価です。
  • 暗黙的なパフォーマンスの期待破ることができます。たとえば、通常、配列内の要素のルックアップはになりますO(1)。ただし、演​​算子のオーバーロードでは、インデックス演算子の実装に応じてsomeobject[i]簡単にO(n)操作できます。

実際には、通常の関数を使用する場合と比較して、演算子のオーバーロードが正当な用途を持つ場合はほとんどありません。正当な例は、数学者が使用する複素数クラスを設計することです。数学者は、数学演算子が複素数に対して定義されるよく知られた方法を理解しています。しかし、これは実際にはあまり一般的なケースではありません。

考慮すべきいくつかの興味深いケース:

  • Lisp:一般に、演算子と関数をまったく区別しません- +単なる通常の関数です。+演算子を含め、好きなように関数を定義できます(通常は、組み込みの関数との競合を避けるために、別々の名前空間で関数を定義する方法があります)。しかし、意味のある関数名を使用する文化的傾向があるため、これはあまり乱用されません。また、Lispではプレフィックス表記が排他的に使用される傾向があるため、演算子のオーバーロードが提供する「構文糖」の価値は低くなります。
  • Javaは -演算子のオーバーロードを禁止します。これはときどきうっとうしいことですが(複素数の場合など)、平均して、おそらく単純な汎用OOP言語として意図されているJavaの正しい設計決定です。Javaコードは、この単純さの結果として、実際には、低/中級の開発者にとって非常に簡単に維持できます。
  • C ++には非常に洗練された演算子のオーバーロードがあります。これは悪用される場合もありcout << "Hello World!"ます(誰か?)パフォーマンスを損なうことなく、希望どおりに。あなたが足で自分を撃った場合、それはあなた自身の責任であると理解されています。

8

この機能の実装は非常に簡単なので、なぜもっと一般的ではないのですか?

実装するの簡単ではありません(簡単に実装しない限り)。また、理想的に実装されていても、あまり得られません:簡潔さによる読みやすさの向上は、不慣れさと不透明度による読みやすさの損失によって相殺されます。要するに、開発者やユーザーの時間の価値がないので、それは珍しいことです。

とはいえ、私はそれを行う3つの言語を考えることができ、それらは異なる方法でそれを行います。

  • スキームであるラケットは、すべてがS-expression-yではない場合に、拡張したい構文のパーサーの量を記述できることを期待します(そして、これを扱いやすくするための便利なフックを提供します)。
  • 純粋に関数型のプログラミング言語であるHaskellでは、句読点のみで構成される演算子を定義でき、固定性レベル(10使用可能)および結合性を提供できます。三項演算子などは、二項演算子と高階関数から作成できます。
  • 依存型のプログラミング言語であるAgdaは、演算子(ここのペーパー)で非常に柔軟であり、if-thenとif-then-elseの両方を同じプログラム内の演算子として定義できますが、そのレクサー、パーサー、およびエバリュエーターはすべて強く結合されています結果として。

4
あなたはそれを「簡単ではない」と言って、すぐにいくつかのとんでもない些細な実装を特徴とする3つの言語をリストしました。結局のところ、それは非常に簡単なことですね。
SKロジック

7

カスタムオペレータが推奨されない主な理由の1つは、どのオペレータも何でもできる/できるからです。

たとえば、cstreamの批判が多かった左シフトの過負荷。

言語で演算子のオーバーロードが許可されている場合、混乱を避けるために、通常は演算子の動作を基本動作と同様に保つことが推奨されます。

また、ユーザー定義の演算子は、特にカスタム設定ルールもある場合、解析をはるかに困難にします。


6
演算子のオーバーロードに関する部分が完全に新しい演算子の定義にどのように適用されるかわかりません。

演算子のオーバーロード(線形代数ライブラリ)の非常に良い使用法を見てきましたが、あなたが言ったように非常に悪いです。これはカスタム演算子に対する良い議論だとは思わない。解析は問題になる可能性がありますが、演算子が期待される場合は明らかです。解析後、演算子の意味を探すのはコードジェネレーター次第です。
beatgammit

3
そして、これはメソッドのオーバーロードとどう違うのですか?
ヨルグW

メソッドのオーバーロードを伴う@JörgWMittagには、(ほとんどの場合)意味のある名前が付けられています。記号を使用すると、何が起こるかを簡潔に説明するのが難しくなります
ラチェットフリーク

1
@ratchetfreak:まあ。+2つのものを-加算し、減算し、*乗算します。私の感じでは、誰もプログラマーに関数/メソッドaddに実際に何かを追加させ、doNothing核兵器を発射させることはありません。そしてa.plus(b.minus(c.times(d)).times(e)、はるかに読みにくいですa + (b - c * d) * e(ボーナスが追加されました-最初の刺し傷は転写のエラーです)。私は...もっと意味のある方法を最初に表示されない
マチェイPiechotkaに

4

ユーザー定義の単語を使用しないのと同じ理由で、ユーザー定義の演算子を使用しません。誰も彼らの機能を「sworp」と呼ばないでしょう。自分の考えを他の人に伝える唯一の方法は、共有言語を使用することです。そして、それはあなたがあなたのコードを書いている社会にとって、言葉と記号(演算子)の両方が知られていなければならないことを意味します。

したがって、プログラミング言語で使用されている演算子は、学校で教えられている演算子(算術演算)またはブール演算子などのプログラミングコミュニティで確立されている演算子です。


1
さらに、機能の背後にある意味を発見する機能を追加します。「sworp」の場合、検索ボックスに簡単に貼り付けてドキュメントを見つけることができます。オペレータを検索エンジンに固定することは、まったく異なる球場です。私たちの現在の検索エンジンは、単に演算子を見つけるのが面倒であり、場合によっては意味を見つけようとして多くの時間を無駄にしています。
マークH

1
どのくらいの「学校」を数えますか?たとえば、∈ elemは素晴らしいアイデアであり、確かに誰もが理解すべき演算子ですが、他の人は同意しないようです。
ティコンジャービス

1
シンボルの問題ではありません。キーボードの問題です。たとえば、unicode beautifierを有効にしてemacs haskell-modeを使用します。また、ASCII演算子をUnicodeシンボルに自動的に変換します。しかし、ASCIIに相当するものがない場合、それを入力することはできません。
Vagifヴェルディ

2
自然言語は、「ユーザー定義語」のみで構成されており、それ以外は何もありません。そして、いくつかの言語は実際にカスタムの単語形成を奨励しています。
SKロジック

4

このようなオーバーロードをサポートする言語については、Scalaは実際にC ++をはるかにクリーンで優れた方法でサポートします。ほとんどの文字は関数名に使用できるため、必要に応じて!+ * = ++などの演算子を定義できます。インフィックスの組み込みサポートがあります(1つの引数を取るすべての関数)。そのような関数の結合性も定義できると思います。ただし、優先順位を定義することはできません(いトリックがある場合のみ、こちらを参照してください)。


4

まだ言及されていないことの1つは、Smalltalkの場合です。Smalltalkでは、すべて(オペレーターを含む)がメッセージ送信です。などの「演算子」は+|実際には単項メソッドです。

すべてのメソッドはオーバーライドできるためa + babが両方とも整数の場合は整数の加算を意味し、両方がOrderedCollectionsの場合はベクトルの加算を意味します。

これらは単なるメソッド呼び出しであるため、優先順位ルールはありません。これは、標準的な数学表記のために重要な意味を持っている:3 + 4 * 5手段(3 + 4) * 5、ではありません3 + (4 * 5)

(これはSmalltalk初心者にとって大きな障害です。数学の規則を破ると、特別なケースが削除されるため、すべてのコード評価が左から右に均一に進行し、言語がはるかに単純になります。)


3

ここでは、2つのことと戦っています。

  1. そもそも演算子が言語に存在するのはなぜですか?
  2. 関数/メソッドに対する演算子の長所は何ですか?

ほとんどの言語では、演算子は実際には単純な関数として実装されていません。関数の足場がいくつかあるかもしれませんが、コンパイラ/ランタイムは、それらのセマンティックな意味と、それらをマシンコードに効率的に変換する方法を明示的に認識しています。これは、組み込み関数と比較してもはるかに当てはまります(ほとんどの実装では、関数呼び出しのすべてのオーバーヘッドが実装に含まれない理由もあります)。ほとんどの演算子は、CPUにあるプリミティブ命令の上位レベルの抽象化です(これが、ほとんどの演算子が算術演算、ブール演算、またはビット単位演算である理由の1つです)。それらを「特別な」関数(「プリミティブ」または「ビルトイン」または「ネイティブ」など)としてモデル化することもできますが、それを行うには一般的にそのような特別な関数を定義するための非常に堅牢なセマンティクスのセットが必要です。別の方法は、意味的にはユーザー定義演算子のように見えるが、それ以外の場合はコンパイラーで特別なパスを呼び出す組み込み演算子を使用することです。これは、2番目の質問に対する答えに反します...

上記で説明した機械翻訳の問題は別として、構文レベルでは、演算子は関数と実際には違いはありません。それらは特徴的で簡潔であり、象徴的である傾向があります。これは、有用でなければならない重要な追加特性を示唆しています:開発者にとって広く理解された意味/意味を持たなければなりません。短い記号は、すでに理解されている一連のセマンティクスの省略形でない限り、あまり意味を伝えません。そのため、ユーザー定義の演算子は本質的に役に立たないので、その性質上、あまり広く理解されていません。1文字または2文字の関数名と同じくらい意味があります。

C ++の演算子オーバーロードは、これを調べるための肥沃な基盤を提供します。ほとんどの演算子オーバーロード「乱用」は、広く理解されているセマンティックコントラクトの一部を破るオーバーロードの形式で提供されます(古典的な例は、a + b!= b + aのようなoperator +のオーバーロード、または+オペランド)。

演算子のオーバーロードユーザー定義の演算子を許可するSmalltalkを見ると、言語がどのようにそれを実行し、どのように役立つかがわかります。Smalltalkでは、演算子は異なる構文プロパティを持つメソッドにすぎません(つまり、中置バイナリとしてエンコードされます)。この言語は、特別な高速化された演算子およびメソッドに「プリミティブメソッド」を使用します。ユーザー定義の演算子が作成された場合、その数は少なく、作成された場合、作成者が使用することを意図していたと思われるほど使用されない傾向があります。演算子のオーバーロードに相当するものもまれです。なぜなら、メソッドの代わりにメソッドとして演算子として新しい関数を定義すると、関数のセマンティクスの表現が可能になるためです。


1

私は常に、C ++の演算子オーバーロードが単一の開発者チームにとって便利なショートカットであることに気付いていましたが、メソッド呼び出しが簡単ではない方法で「隠されている」という理由だけで、長期的にはあらゆる種類の混乱を引き起こしますdoxygenのようなツールを分解し、人々がイディオムを適切に使用するために理解する必要があります。

場合によっては、想像するよりも理解するのがはるかに難しいこともあります。むかしむかし、大規模なクロスプラットフォームC ++プロジェクトで、別の連結にoperator /を使用するFilePathオブジェクト(JavaのFileオブジェクトに類似)を作成してパスの構築方法を正規化することをお勧めしますパス部分(これFile::getHomeDir()/"foo"/"bar"により、サポートされているすべてのプラットフォームで適切な処理を実行できます)。それを見た人は誰でも、「一体何なの?弦の分割?...ああ、それはかわいいが、正しいことをすることを信用していない」と言うでしょう。

同様に、Matrix * Matrix、Vector * Vector(ドット)、Vector%Vector(クロス)、Matrix * Vector(マトリックス変換)、Matrix ^ Vector(同次座標を無視する特殊なケースのマトリックス変換-表面法線に便利)など、ただし、ベクトル数学ライブラリを書いた人の解析時間を少し節約しますが、終了するだけです他の人にとってこの問題をさらに混乱させます。それだけの価値はありません。


0

演算子のオーバーロードは、メソッドのオーバーロードが悪い考えであるのと同じ理由で悪い考えです。画面上の同じシンボルは、周囲にあるものに応じて異なる意味を持ちます。これにより、カジュアルな読書がより困難になります。

可読性は保守性の重要な側面であるため、常にオーバーロードを避ける必要があります(非常に特殊な場合を除く)。各シンボル(演算子または英数字の識別子)が独自の意味を持つことは、はるかに優れています。

例:なじみのないコードを読んでいるときに、知らない新しいalphanum識別子に出会うと、少なくとも、知らないということがわかるという利点があります。。その後、それを調べに行くことができます。ただし、意味を知っている共通の識別子または演算子が表示される場合、実際にはオーバーロードされて完全に異なる意味を持つことに気付く可能性ははるかに低くなります。どの演算子がオーバーロードされているか(オーバーロードを広く使用するコードベースで)を知るには、たとえその一部だけを読みたい場合でも、完全なコードの実用的な知識が必要です。これにより、新しい開発者をそのコードでスピードアップするのが難しくなり、小さな仕事に人々を連れて行くことができなくなります。これはプログラマの仕事のセキュリティには良いかもしれませんが、コードベースの成功に責任がある場合は、この方法を一切避けてください。

演算子はサイズが小さいため、演算子をオーバーロードするとコードの密度が高くなりますが、コードを高密度にすることは実際の利点ではありません。ロジックが2倍の行は、読み取りに2倍の時間がかかります。コンパイラは気にしません。唯一の問題は人間が読みやすいことです。コードをコンパクトにしても読みやすさは向上しないため、コンパクト化のメリットはほとんどありません。先に進んでスペースを取り、一意の操作に一意の識別子を与えると、コードは長期的にはより成功します。


「画面上の同じシンボルは、周囲にあるものに応じて異なる意味を持ちます」-ほとんどの言語の多くのオペレーターにとってはすでにそうです。
rkj

-1

優先順位と複雑な解析を処理するための技術的な困難はさておき、プログラミング言語が何を考慮する必要があるかについて、いくつかの側面があると思います。

演算子は通常、コア言語で適切に定義および文書化された短い論理構成体です(比較、割り当て)。また、通常、ドキュメントなしでは理解するのが困難です(たとえば、比較a^bxor(a,b)てください)。通常のプログラミングでは実際に意味をなす演算子の数はかなり限られています(>、<、=、+など)。

私の考えは、言語で明確に定義された演算子のセットに固執することです-そして、それらの演算子の演算子のオーバーロードを許可します(演算子が同じことを行うが、カスタムデータ型を使用するという柔らかい推奨を与えられました)。

~およびのユースケースは、|単純な演算子のオーバーロード(C#、C ++など)で実際に可能になります。DSLは有効な使用領域ですが、おそらく私の唯一の有効な領域の1つです。ただし、新しい言語を作成するためのより良いツールがあると思います。別の言語内で真のDSL言語を実行することは、これらのコンパイラコンパイラツールを使用してもそれほど難しくありません。「LUA引数の拡張」についても同様です。言語は、主に特定の方法で問題を解決するために定義されている可能性が高く、サブ言語の基礎ではありません(例外が存在します)。


-1

もう1つの要因は、使用可能な演算子を使用して操作を定義することが必ずしも簡単ではないことです。つまり、どんな種類の数字でも、「*」演算子は意味をなすことができ、通常は言語または既存のモジュールで実装されます。しかし、定義する必要がある典型的な複雑なクラス(ShipingAddress、WindowManager、ObjectDimensions、PlayerCharacterなどのようなもの)の場合、その動作は明確ではありません。2つのアドレスを乗算しますか?

確かに、文字列をShippingAddressクラスに追加すると、「住所の行1を置換」(「setLine1」関数の代わり)などのカスタム操作を意味し、数字を追加すると「郵便番号を置換」(「setZipCode」の代わり) 、しかし、コードは非常に読みやすく、混乱しません。通常、演算子は基本的な型/クラスで使用されると考えています。その動作は直感的で明確で一貫性があるためです(少なくとも言語に精通している場合)。Integer、String、ComplexNumbersなどの型を考えてください。

そのため、特定のケースで演算子を定義することが非常に役立つ場合でも、実際の実装は非常に限られています。明らかに成功するケースの99%は、基本的な言語パッケージに既に実装されています。

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