演算子はキーワードや関数よりも読みやすいですか?[閉まっている]


14

それは少し主観的ですが、どの要因がオペレーターを使いやすくするのか、鈍くて難しくするのかをより明確に理解したいと思っています。私は最近、言語の設計を検討してきましたが、私が常に振り返る問題の1つは、言語の重要な操作をいつ演算子にするか、キーワードや関数をいつ使用するかです。

Haskellは、カスタムオペレーターの作成が簡単であり、多くの場合、新しいデータ型が複数のオペレーターで使用できるようにパッケージ化されるため、これについてはやや悪名高いです。たとえば、Parsecライブラリには、パーサーを結合するための多数の演算子が付属して>.おり、.> 今のような宝石を覚えていますが、今の意味を思い出すことさえできませんが、彼らは実際に意味します。などの関数呼び出しleftCompose(parser1, parser2)は改善されましたか?確かにより冗長ですが、いくつかの点で明確です。

Cライクな言語での演算子のオーバーロードも同様の問題ですが、おなじみの演算子のよう+な意味を通常とは異なる新しい意味でオーバーロードするという追加の問題によって混同されます。

新しい言語では、これはかなり難しい問題のように思えます。たとえば、F#では、キャストはC#スタイルのキャスト構文または冗長VBスタイルの代わりに、数学的に導出された型キャスト演算子を使用します。C#:(int32) xVB:CType(x, int32)F#:x :> int32

理論的には、新しい言語にはほとんどの組み込み機能の演算子があります。変数宣言の代わりにdefdecまたはvar変数宣言のために、なぜそうでない! nameか、@ nameまたは類似した何か。それは確かに、宣言に続くバインディングを短縮します。@x := 5代わりに、declare x = 5またはlet x = 5 ほとんどのコードは多くの変数定義を必要とするでしょう。

オペレーターが明確で有用なのはいつですか?



1
Javaの演算子のオーバーロードがないと私に不平を言ったことのある1,000人の開発者の一人がこの質問に答えることを望みます。
smp7d

1
質問の入力を終えたときにAPLを考えていましたが、いくつかの点で、ポイントを明確にし、主観的ではない領域に移しました。APLはその極端な数の演算子によって使いやすさで何かを失うことにほとんどの人が同意できると思いますが、言語に演算子のオーバーロードがない場合に文句を言う人の数は確かにいくつかの機能に適していることを支持します演算子として機能します。禁止されているカスタムオペレーターとオペレーター以外の何者との間でも、実用的な実装と使用のためのガイドラインを隠さなければなりません。
CodexArcanum

私が見るように、演算子のオーバーロードの欠如は、ユーザー定義型よりもネイティブ型に特権を与えるため、好ましくありません(Javaは他の方法でもこれを行うことに注意してください)。Haskellスタイルのカスタムオペレーターについて、私はもっと曖昧です。これはトラブルへの開かれた招待のように思えます
...-comingstorm

2
@comingstorm:実際、Haskellの方法の方が優れていると思います。さまざまな方法でオーバーロードできる演算子の有限セットがある場合、多くの場合、さまざまなコンテキストで演算子を再利用することを余儀なくされます(たとえば+、文字列の連結または<<ストリーム)。一方、Haskellでは、演算子はカスタム名(つまり、オーバーロードされていない)を持つ関数か、型クラスの一部のいずれかです。つまり、ポリモーフィックではあるが、すべての型に対して同じ論理的なことを行います。同じ型の署名もあります。だから、>>ある>>種類ごとに、ビットシフトになるだろうことはありません。
ティコンジェルビス

回答:


16

一般的な言語設計の観点からは、関数と演算子に違いはありません。関数は、任意の(または可変の)アリティを持つプレフィックス演算として説明できます。また、キーワードは、予約された名前を持つ単なる関数または演算子と見なすことができます(これは、文法の設計に役立ちます)。

これらの決定はすべて、最終的に表記をどのように読みたいか、そしてあなたが言うように主観的であることに帰着しますが、いくつかの明白な合理化を行うことができます。

最後に、ダイクストラは、彼が使用した数学表記法の興味深い正当化を書きました。


4
ダイクストラの論文へのリンクに2つ目の+1を提供したいと思います。
AProgrammer

素晴らしいリンクです。PayPalアカウントを公開する必要がありました!;)
アドリアーノ・レペティ

8

私にとって、オペレーターは、コード行を声に出して読むことも頭の中で読むこともできなくなって意味をなさなくなったら、役に立たなくなります。

たとえば、declare x = 5として読む"declare x equals 5"か、として読むlet x = 5ことができます"let x equal 5"。これは大声で読むと非常に理解しやすいですが、読書@x := 5"at x colon equals 5"(または"at x is defined to be 5"数学者の場合)として読むので、まったく意味がありません。

ですから、私の意見では、コードを声に出して読み、言語に精通していない合理的な知識のある人が理解できる場合は演算子を使用しますが、そうでない場合は関数名を使用します。


5
私は@x := 5理解するのは難しいとは思わない-私はまだ自分自身にそれを声に出して読みますset x to be equal to 5
FrustratedWithFormsDesigner

3
その場合、@ Rachelは、:=プログラミング言語以外の数学表記でより広く使用されます。また、声明x = x + 1は少しばかげたものになります。
-ccoakley

3
皮肉なことに、私は一部の人々が:=割り当てとして認識しないことを忘れていました。いくつかの言語は実際にそれを使用します。Smalltalkは、おそらく最もよく知られていると思います。割り当てと平等を別々の演算子にするかどうかは、まったく別のワームの缶であり、おそらく別の質問ですでにカバーされています。
CodexArcanum

1
@ccoakleyありがとう、私はそれ:=が数学で使われていることを知りませんでした。私が見つけた定義の1つは「である」と定義されていたので@x := 5、英語でに翻訳され"at x is defined to be 5"ます。
レイチェル

1
確かにPascalはASCIIに左矢印がないため、割り当てとして:=を使用しました。
バティーン

4

演算子は、慣れていると明確で便利です。そして、それはおそらく、既に確立された慣行として、選択された演算子が(フォーム、優先順位、および結合性のように)十分に近い場合にのみ、演算子のオーバーロードを行うことを意味します。

しかし、もう少し掘り下げてみると、2つの側面があります。

構文(関数呼び出し構文のプレフィックスとは対照的な演算子のインフィックス)と命名。

構文については、接頭辞よりも中置が十分に明確であるため、慣れる努力が必要な場合があります:呼び出しが連鎖されネストされている場合。

比較するa * b + c * d + e * f+(+(*(a, b), *(c, d)), *(e, f))、または+ + * a b * c d * e f(両方のインフィックスバージョンと同じ条件で解析可能です)。事実+代わりに長い道のりでそれらの1つ前の条件を区切るには、私の目でそれを読みやすく(ただし、接頭辞構文のために必要されていない優先順位のルールを覚えておく必要)。このように物事を複雑にする必要がある場合、長期的な利益の方法は学習コストの価値があります。また、記号を使用する代わりに文字で演算子に名前を付けても、この利点を維持します。

演算子の命名に関しては、確立されたシンボルまたは明確な名前以外の何かを使用する利点はほとんどありません。はい、それらはより短いかもしれませんが、それらは本当に不可解であり、あなたがそれらを既に知っている正当な理由がないならば、すぐに忘れられます。

言語設計POVを使用する場合の3番目の側面は、演算子のセットを開くか閉じるか(演算子を追加できるかどうか)、および優先順位と結合性がユーザー指定可能かどうかです。私が最初に取るのは、注意してユーザー指定可能な優先度と結合性を提供しないことです便利な場合は中置構文の恩恵を受けるだけです)。


ユーザーが演算子を追加できる場合、追加した演算子の優先順位と結合性をユーザーに設定させてみませんか?
ティコンジェルビス

@TikhonJelvis、それはほとんど保守的でしたが、例以外のものにそれを使用できるようにしたい場合、考慮すべきいくつかの側面があります。たとえば、オーバーロードされたセットの特定のメンバーではなく、優先度と結合性を演算子にバインドします(解析の完了後にメンバーを選択した場合、選択は解析に影響しません)。したがって、同じ演算子を使用してライブラリを統合するには、その優先度と結合性に同意する必要があります。可能性を追加する前に、Algol 68に続く言語がほとんどないため(AFAIKなし)、言語を定義できる理由を見つけようとしました。
AProgrammer

Haskellでは、任意の演算子を定義して、優先順位と結合性を設定できます。違いは、演算子自体をオーバーロードできないことです。通常の関数のように動作します。これは、異なるものに対して同じ演算子を使用しようとする異なるライブラリを持つことができないことを意味します。独自の演算子を定義できるため、同じものを異なるものに再利用する理由がはるかに少なくなります。
ティコンジェルビス

1

記号としての演算子は、直感的に理解できる場合に役立ちます。 +そして-、例えば加算と減算は明らかです。だから、ほとんどすべての言語が演算子としてそれらを使用しているのです。比較と同じ(<>小学校で教えられている、と<=とは、>=あなたが標準的なキーボードには下線が引かれ、比較キーがないことを理解する際の基本的な比較を直感的に拡張したものです。)

*そして/あまりすぐに明らかにされているが、彼らは普遍的に使用していると、右隣にテンキー上の位置+-キーは、コンテキストを提供するのに役立ちます。Cさん<<>>同じカテゴリにある:あなたは左シフトと右シフトが何であるかを理解したとき、それは矢の後ろのニーモニックを理解することは、あまりにも難しいことではありません。

次に、Cコードを判読不能なオペレータスープに変えることができる、本当に奇妙なものを見つけます。(ここでCを取り上げるのは、Cでの作業または子孫言語での構文を通じて、ほとんどすべての人が構文をよく知っている非常に演算子が多い例であるためです。)たとえば、%演算子 誰もがそれが何であるかを知っています:それはパーセント記号です...そうですか?Cを除き、100による除算とは関係ありません。モジュラス演算子です。それはニーモニックにゼロを意味しますが、そこにあります!

さらに悪いのはブール演算子です。 &ようとすると、二つの異なるがあることを除いて、理にかなっている&二つの異なることを行う、との両方が、そのうちの一つは、それらの一方のみがこれまでにもかかわらず、有効であるどのような場合には構文的に有効な演算子、実際に理にかなっている任意の場合には。それは混乱のただのレシピです!またはXORない事業者は、彼らがニーモニック便利な記号を使用しないため、さらに悪化しており、どちらか一貫していません。(double- xor演算子はありません。論理演算子とビット演算子は、1つのシンボルと2倍のバージョンの代わりに2つの異なるシンボルを使用しません。)

さらに悪いことに、&and *演算子は完全に異なるものに再利用されるため、人とコンパイラの両方が解析するのが難しくなります。(どういうA * B意味ですか?それは完全にコンテキストに依存します:A型または変数ですか?)

確かに、私はデニス・リッチーと話をしたことはなく、彼が言語で行った設計上の決定について尋ねましたが、その多くは演算子の背後にある哲学が「記号のための記号」であるように感じました。

これとは対照的に、パスカルはCと同じ演算子を持ちますが、それらを表現する上で異なる哲学を持っています。演算子は直感的で読みやすいものでなければなりません。 算術演算子は同じです。モジュラス演算子は単語ですmod。なぜなら、キーボードには明らかに「モジュラス」を意味する記号がないからです。論理演算子はandorxorそしてnot、それぞれの一つだけがあります。コンパイラーは、ブール演算か数値演算のどちらを使用しているかに基づいて、ブールバージョンとビット単位バージョンのどちらが必要かを認識し、エラーのクラス全体を排除します。これにより、特に構文が強調表示される最新のエディターでは、演算子とキーワードが識別子と視覚的に区別されるため、PascalコードはCコードよりもはるかに理解しやすくなります。


4
パスカルはまったく別の哲学を表していません。あなたはワースの論文を読めば、彼はのようなもののために記号を使用andし、orすべての時間。しかし、Pascalを開発したとき、彼は6ビット文字を使用したControl Dataメインフレームでそれを行いました。そのため、文字セットにはASCIIやASCIIのような記号用のスペースほとんどないという単純な理由から、多くのことに単語を使用することを決定しました。私はCとPascalの両方を使用しましたが、Cはかなり読みやすいと思います。パスカルを好むかもしれませんが、それは好みではなく、事実です。
ジェリー

Wirthに関する@JerryCoffinの発言に追加するために、その後の言語(Modula、Oberon)はより多くのシンボルを使用しています。
AProgrammer

@AProgrammer:そして、彼らはどこにも行かなかった。;)
メイソン・ウィーラー

私は|(そして、拡張により||)意味があると思います。また、文法のような他のコンテキストで常に交替が見られ、2つのオプションを分割しているように見えます。
ティコンジェルビス

1
文字ベースのキーワードの利点は、それらのスペースが記号よりもはるかに大きいことです。たとえば、Pascalスタイルの言語には、「モジュラス」と「剰余」の両方、および整数と浮動小数点の除算(オペランドのタイプを超えて異なる意味を持つ)の演算子を含めることができます。
supercat 14

1

演算子は、その機能が使い慣れた数学的な目的を厳密に反映している場合、はるかに読みやすいと思います。たとえば、加算や減算を実行する場合、+、-などの標準演算子は、加算や減算よりも読みやすくなります。オペレーターの動作を明確に定義する必要があります。たとえば、リスト連結の+の意味のオーバーロードは許容できます。理想的には、操作に副作用はなく、変更する代わりに値を返します。

ただし、foldなどの関数のショートカット演算子には苦労しました。明らかに彼らとより多くの経験はこれを緩和するだろうが、私は見つけることfoldRightよりも読みやすいです/:


4
おそらく使用頻度もそれに含まれます。foldオペレーターが多くの時間を節約できるほど頻繁にあなたが思うとは思いません。それはまた、LISPy(+ 1 2 3)が奇妙に見えるが(1 2 3を追加)そうではない理由でもあります。演算子は厳密にバイナリと考える傾向があります。Parsecの>>.スタイル演算子は間抜けかもしれませんが、少なくともParsecで行う最も重要なことは、パーサーを結合することです。
CodexArcanum

1

演算子の問題は、代わりに使用できるセンシティブな(!)メソッド名の数と比較して、演算子の数が少ないことです。副作用は、ユーザー定義可能な演算子が多くのオーバーロードを導入する傾向があることです。

確かに、この行C = A * x + b * cはに比べて読み書きが簡単ですC = A.multiplyVector(x).addVector(b.multiplyScalar(c))

または、頭の中にすべての演算子のオーバーロードされたバージョンがあれば、間違いなく簡単に書くことができます。そして、最後から2番目のバグを修正しながら、後で読むことができます。

さて、そこで実行されるほとんどのコードについては、それで問題ありません。「プロジェクトはすべてのテストに合格し、実行されます」-多くのソフトウェアにとって、私たちが望んでいたすべてのものです。

しかし、重要な分野に進出するソフトウェアを使用している場合、物事は異なる方法で動作します。安全性が重要なもの。セキュリティ。飛行機を空中に保つソフトウェア、または核施設を稼働させ続けるソフトウェア。または、機密性の高いメールを暗号化するソフトウェア。

コードの監査を専門とするセキュリティの専門家にとっては、これは悪夢です。多数のユーザー定義演算子と演算子のオーバーロードが混在すると、最終的に実行されるコードを把握するのが難しいという不快な状況になります。

したがって、元の質問で述べたように、これは非常に主観的な主題です。また、演算子の使用方法に関する多くの提案は完全に理にかなっているように思えるかもしれませんが、それらのほんの一部の組み合わせは、長期的には多くの問題を引き起こす可能性があります。


0

最も極端な例はAPLで、次のプログラムは「人生のゲーム」だと思います。

life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}

そして、リファレンスマニュアルで数日を費やすことなく、それがすべての意味を理解できるなら、幸運を祈ります!

問題は、ほぼ全員が直感的に理解できる一連のシンボルがあることです。 +,-,*,/,%,=,==,&

そして、理解する前に説明が必要な残りの部分は、一般的に特定の言語に固有です。たとえば、[]、()、さらには "。"でさえ、配列のメンバーを指定する明確な記号はありません。使用されていますが、同様に簡単に使用できる明らかなキーワードがないため、演算子を慎重に使用する場合があります。しかし、それほど多くはありません。

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