web-dev-qa-db-ja.com

なぜほとんどの主流言語は3方向ブール比較の「x <y <z」構文をサポートしないのですか?

2つの数値(または他の整然としたエンティティ)を比較する場合は、x < yを使用して比較します。それらの3つを比較したい場合は、高校の代数学の学生がx < y < zを試すことを勧めます。私のプログラマーは、「いいえ、それは無効です。あなたはx < y && y < zを実行する必要があります」と応答します。

私が出会ったほとんどの言語はこの構文をサポートしていないようです。これは数学でどれほど一般的であるかを考えると奇妙です。 Pythonは注目に値する例外です。JavaScriptは例外のように見えますが、実際には演算子の不幸な副産物にすぎません優先順位と暗黙の変換。node.jsでは、1 < 3 < 2は実際には(1 < 3) < 2 === true < 2 === 1 < 2であるため、trueと評価されます。

だから、私の質問はこれです:なぜx < y < zが期待されるセマンティクスでプログラミング言語で一般的に利用できないのですか?

34
JesseTG

これらは2項演算子であり、連鎖すると、通常は自然に次のような抽象構文ツリーが生成されます。

normal abstract syntax tree for binary operators

評価すると(葉っぱから行う)、これはx < yからブール値の結果を生成し、次にboolean < zを実行しようとすると型エラーが発生します。 x < y < zが上記のように機能するためには、コンパイラーで特別なケースを作成して、次のような構文ツリーを作成する必要があります。

special case syntax tree

これを行うことは不可能ではありません。明らかにそうですが、実際にはそれほど頻繁に発生しないケースのために、パーサーにいくらかの複雑さを追加します。基本的には、2項演算子のように機能し、3項演算子のように効果的に機能するシンボルを作成します。これには、エラー処理などのすべての影響が伴います。これは、言語デザイナーが可能な限り回避したほうがよい、問題が発生するための多くのスペースを追加します。

31
Karl Bielefeldt

プログラミング言語でx < y < zが一般的に利用できないのはなぜですか?

この回答で私はそれを結論付けます

  • この構成は言語の文法で実装するのは簡単であり、言語ユーザーに価値をもたらしますが、
  • これがほとんどの言語に存在しない主な理由は、他の機能と比較したその重要性と、言語の管理機関がどちらにも意欲がないためです
    • 潜在的に重大な変更でユーザーを混乱させる
    • 機能を実装するために移動します(つまり、遅延)。

前書き

私はこの問題についてPythonistの視点から話すことができます。私はこの機能を備えた言語のユーザーであり、言語の実装の詳細を研究するのが好きです。これ以外にも、CやC++などの言語の変更プロセスにある程度精通しており(ISO標準は委員会によって管理され、年ごとにバージョン管理されます)、RubyとPythonの両方を監視しました。 _重大な変更を実装します。

Pythonのドキュメントと実装

Docs/grammarから、比較演算子を使用して任意の数の式をチェーンできることがわかります。

comparison    ::=  or_expr ( comp_operator or_expr )*
comp_operator ::=  "<" | ">" | "==" | ">=" | "<=" | "!="
                   | "is" ["not"] | ["not"] "in"

とドキュメントはさらに述べています:

比較は任意に連鎖できます。たとえば、x <y <= zはx <yおよびy <= zと同等ですが、yは1回しか評価されません(ただし、どちらの場合もx <yが見つかるとzはまったく評価されません)偽である)。

論理的同等性

そう

result = (x < y <= z)

xy、およびzの評価に関して論理的にequivalentで、 yが2回評価されるという例外:

x_lessthan_y = (x < y)
if x_lessthan_y:       # z is evaluated contingent on x < y being True
    y_lessthan_z = (y <= z)
    result = y_lessthan_z
else:
    result = x_lessthan_y

繰り返しますが、yは(x < y <= z)で1回だけ評価されるという違いがあります。

(括弧は完全に不要で冗長ですが、他の言語の括弧を利用するために使用しました。上記のコードは合法的なPythonです。)

解析された抽象構文ツリーの検査

Pythonが連鎖比較演算子を解析する方法を調べることができます:

>>> import ast
>>> node_obj = ast.parse('"foo" < "bar" <= "baz"')
>>> ast.dump(node_obj)
"Module(body=[Expr(value=Compare(left=Str(s='foo'), ops=[Lt(), LtE()],
 comparators=[Str(s='bar'), Str(s='baz')]))])"

ですから、これはPythonや他の言語が解析するのが本当に難しくないことがわかります。

>>> ast.dump(node_obj, annotate_fields=False)
"Module([Expr(Compare(Str('foo'), [Lt(), LtE()], [Str('bar'), Str('baz')]))])"
>>> ast.dump(ast.parse("'foo' < 'bar' <= 'baz' >= 'quux'"), annotate_fields=False)
"Module([Expr(Compare(Str('foo'), [Lt(), LtE(), GtE()], [Str('bar'), Str('baz'), Str('quux')]))])"

そして、現在受け入れられている答えとは異なり、三項演算は一般的な比較演算であり、最初の式、特定の比較の反復可能、および式ノードの反復可能を必要に応じて評価します。シンプル。

Pythonに関する結論

私は個人的に範囲のセマンティクスが非常にエレガントであると感じています。私が知っているほとんどのPython専門家は、機能を損なうと考えるのではなく、機能の使用を奨励します-セマンティクスは評判の良いドキュメントで非常に明確に述べられています(上記のとおり)。

コードが書かれているよりもはるかに多く読み取られることに注意してください。 Fear、Uncertainty、およびDoubtの一般的な見解を上げることで軽視するのではなく、コードを読みやすくする変更を採用する必要があります。

では、なぜx <y <zがプログラミング言語で一般的に利用できないのですか?

私は、機能の相対的な重要性と、言語の知事によって許可された変化の相対的な勢い/慣性を中心とした理由の合流があると思います。

他のより重要な言語機能について同様の質問をすることができます

JavaまたはC#で多重継承が利用できないのはなぜですか? どちらの質問にも良い答えはありません。ボブマーティンが主張するように、おそらく開発者はあまりにも怠惰であり、与えられた理由は単に言い訳にすぎません。また、多重継承は、コンピュータサイエンスのかなり大きなトピックです。それは確かに演算子の連鎖よりも重要です。

簡単な回避策があります

比較演算子の連鎖は洗練されていますが、多重継承ほど重要ではありません。 JavaとC#が回避策としてインターフェイスを持っているのと同じように、複数の比較のためのすべての言語もそうです-比較をブールの「and」でチェーンするだけで、十分に簡単に機能します。

ほとんどの言語は委員会によって管理されています

ほとんどの言語は委員会によって進化しています(Pythonのような賢明な慈悲深い独裁者がいるのではなく)。そして、私はこの問題が、それぞれの委員会から脱出するのに十分なサポートを見ていなかったと推測しています。

この機能を提供しない言語は変更できますか?

言語が期待される数学的セマンティクスなしでx < y < zを許可する場合、これは重大な変更になります。そもそもそれを許可していなければ、追加するのはほとんど簡単でしょう。

重大な変更

重大な変更が加えられた言語について:動作の重大な変更で言語を更新しますが、ユーザーはこれを好まない傾向があります。ユーザーがx < y < zの以前の動作に依存している場合、大声で抗議する可能性があります。そして、ほとんどの言語は委員会によって管理されているので、私はそのような変化を支持するために多くの政治的意志を得るとは思えません。

37
Aaron Hall

コンピューター言語は、可能な限り最小の単位を定義し、それらを組み合わせられるようにします。最小の可能な単位は、ブール値の結果を与える「x <y」のようなものです。

三項演算子を要求することができます。例はx <y <zです。次に、どのような演算子の組み合わせを許可しますか?明らかにx> y> zまたはx> = y> = zまたはx> y> = zまたは多分x == y == zが許可されるべきです。 x <y> zはどうですか? x!= y!= z?最後の1つは、x!= yとy!= zの意味、または3つすべてが異なるという意味ですか?

引数の昇格:CまたはC++では、引数は共通の型に昇格されます。では、xのx <y <zがdoubleであることはどういう意味ですか?yとzはlong long intですか? 3つすべてがダブルに昇格しましたか?または、yは2倍として、もう1度はintとして取得されます。 C++で演算子の一方または両方がオーバーロードされた場合はどうなりますか?

そして最後に、任意の数のオペランドを許可しますか? a <b> c <d> e <f> gのように?

まあ、それはすべて非常に複雑になります。ここで気にしないのは、構文エラーが発生するx <y <zです。なぜなら、x <y <zが実際に何をしているのか理解できない初心者に与えるダメージと比較すると、その有用性は小さいからです。

13
gnasher729

多くのプログラミング言語では、x < yは2つのオペランドを受け入れ、が単一のブール結果に評価されるバイナリ式です。したがって、複数の式をチェーンする場合、true < zおよびfalse < zは意味がなく、これらの式が正常に評価された場合、誤った結果が生成される可能性があります。

x < yは、2つのパラメーターを取り、単一のブール結果を生成する関数呼び出しと考える方がはるかに簡単です。実際、それは多くの言語が内部で実装する言語です。構成可能で、簡単にコンパイルでき、動作します。

x < y < zシナリオは、はるかに複雑です。これで、コンパイラーは事実上、3つの関数を作成する必要があります:x < yy < z、およびこれら2つの値の結果まとめると、すべてが間違いなく あいまいな言語の文法 のコンテキスト内にあります。

なぜ彼らはそれを逆にしたのですか?それは明確な文法であるため、実装がはるかに簡単で、正確にするのがはるかに簡単です。

10
Robert Harvey

ほとんどの主流言語は(少なくとも部分的に)オブジェクト指向です。基本的に、OOの基本的な原理は、オブジェクトが他のオブジェクト(または自分自身)にメッセージを送信し、レシーバーそのメッセージのは、そのメッセージへの応答方法を完全に制御します。

では、次のようなものを実装する方法を見てみましょう

_a < b < c
_

厳密に左から右(左結合)で評価できます。

_a.__lt__(b).__lt__(c)
_

しかし、a.__lt__(b)の結果に対して___lt___を呼び出します。これはBooleanです。それは意味がありません。

右連想を試してみましょう:

_a.__lt__(b.__lt__(c))
_

いや、それも意味がありません。これでa < (something that's a Boolean)ができました。

さて、それを構文糖として扱うのはどうでしょう。 nのチェーンを作成しましょう_<_比較はn-1-aryメッセージを送信します。つまり、メッセージ____lt___をaに送信し、bおよびcを引数として渡します。

_a.__lt__(b, c)
_

さて、それは機能しますが、ここで奇妙な非対称性があります。aは、bより小さいかどうかを判断します。しかし、bcより小さいかどうかを判断することはできません。代わりに、その判断はaによってalsoによって行われます。

thisに送信されたn-aryメッセージとして解釈するのはどうですか?

_this.__lt__(a, b, c)
_

最終的に!これは機能します。ただし、オブジェクトの順序はオブジェクトのプロパティではなくなります(たとえば、abより小さいかどうかは、aのプロパティでもbのプロパティでもありません)ではなく、context(つまり、this)。

奇妙に見える主流の見地から。ただし、 Haskellでは、それは正常です。たとえば、Ordタイプクラスの複数の異なる実装が存在する可能性があり、abより小さいかどうかは、どのタイプクラスインスタンスがスコープ内にあるかによって異なります。

しかし、実際には、それはそれは奇妙なことではありません! Java( Comparator )と.NET( IComparer )の両方に、独自の順序関係をソートアルゴリズムなどに挿入できるインターフェースがあります。したがって、順序付けは型に固定されるものではなく、コンテキストに依存することを、彼らは完全に認めます。

私の知る限り、現在、そのような翻訳を実行する言語はありません。ただし、優先順位があります。 IokeSeph の両方に、デザイナーが「3項演算子」と呼ぶものがあります。–構文的に演算子バイナリですが、意味的にはバイナリです。特に、

_a = b
_

isnotメッセージを_=_からabを引数として渡すと解釈されますが、メッセージ_=_を「現在のグラウンド」(thisと似ているが同一ではない概念)は、abを引数として渡します。したがって、_a = b_は次のように解釈されます

_=(a, b)
_

ではなく

_a =(b)
_

これは、n項演算子に簡単に一般化できます。

これは本当にOO言語に固有です。OOでは、メッセージ送信の解釈を最終的に担当する単一のオブジェクトが常に存在します。これまで見てきたように、 _a < b < c_などのオブジェクト.

ただし、これは手続き型言語や関数型言語には適用されません。たとえば、 SchemeCommon LISP 、および Clojure では、_<_関数はn項であり、次のように呼び出すことができます。任意の数の引数。

特に、_<_はではないは「より小」を意味し、これらの関数の解釈は少し異なります。

_(<  a b c d) ; the sequence a, b, c, d is monotonically increasing
(>  a b c d) ; the sequence a, b, c, d is monotonically decreasing
(<= a b c d) ; the sequence a, b, c, d is monotonically non-decreasing
(>= a b c d) ; the sequence a, b, c, d is monotonically non-increasing
_
6
Jörg W Mittag

それは単に言語デザイナーがそれを考えなかったか、それが良い考えだとは考えなかったからです。 Pythonは、単純な(ほぼ)LL(1)文法で説明したようにそれを行います。

3
Neil G

次のC++プログラムは、警告を最高レベル(-Weverything)に設定していても、clangからnaryのぞき見でコンパイルします。

#include <iostream>
int main () { std::cout << (1 < 3 < 2) << '\n'; }

一方、GNUコンパイラスイートはcomparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]であることを警告します。

だから、私の質問はこれです:期待されるセマンティクスで、プログラミング言語でx <y <zが一般的に利用できないのはなぜですか?

答えは簡単です。下位互換性です。 1<3<2に相当するものを使用し、結果が本物らしく見えることを期待するコードが大量に出回っています。

言語設計者がこれを「正しく」行うチャンスは1つしかありませんが、それが言語が最初に設計された時点です。最初に「間違った」とは、他のプログラマーがその「間違った」動作をすぐに利用することを意味します。 2回目に「正しく」取得すると、既存のコードベースが機能しなくなります。

2
David Hammen