it-swarm.com.de

Warum unterstützen die meisten Mainstream-Sprachen die "x <y <z" -Syntax für 3-Wege-Boolesche Vergleiche nicht?

Wenn ich zwei Zahlen (oder andere gut geordnete Entitäten) vergleichen möchte, würde ich dies mit x < y Tun. Wenn ich drei davon vergleichen möchte, schlägt der Algebra-Schüler vor, x < y < z Zu versuchen. Der Programmierer in mir antwortet dann mit "Nein, das ist nicht gültig, Sie müssen x < y && y < z Tun".

Die meisten Sprachen, auf die ich gestoßen bin, scheinen diese Syntax nicht zu unterstützen, was seltsam ist, wenn man bedenkt, wie häufig sie in der Mathematik vorkommt. Python ist eine bemerkenswerte Ausnahme. JavaScript sieht wie eine Ausnahme aus, ist aber wirklich nur ein unglückliches Nebenprodukt des Operators Vorrang und implizite Konvertierungen; in node.js wird 1 < 3 < 2 zu true ausgewertet, da es sich wirklich um (1 < 3) < 2 === true < 2 === 1 < 2 handelt.

Meine Frage lautet also: Warum ist x < y < z In Programmiersprachen mit der erwarteten Semantik nicht allgemein verfügbar?

34
JesseTG

Dies sind binäre Operatoren, die, wenn sie verkettet sind, normalerweise und natürlich einen abstrakten Syntaxbaum erzeugen wie:

(normal abstract syntax tree for binary operators

Bei der Auswertung (was Sie von den Blättern nach oben tun) ergibt sich ein boolesches Ergebnis aus x < y, dann erhalten Sie einen Tippfehler beim Versuch, boolean < z. Damit x < y < z Um wie beschrieben zu arbeiten, müssen Sie im Compiler einen Sonderfall erstellen, um einen Syntaxbaum wie folgt zu erstellen:

(special case syntax tree

Nicht, dass das nicht möglich wäre. Es ist offensichtlich, aber es erhöht die Komplexität des Parsers für einen Fall, der nicht wirklich so oft auftritt. Sie erstellen im Grunde genommen ein Symbol, das sich manchmal wie ein binärer Operator und manchmal effektiv wie ein ternärer Operator verhält, mit allen Auswirkungen der Fehlerbehandlung und der damit verbundenen Auswirkungen. Das schafft viel Platz für Fehler, die Sprachdesigner nach Möglichkeit lieber vermeiden würden.

31
Karl Bielefeldt

Warum ist x < y < z In Programmiersprachen nicht allgemein verfügbar?

In dieser Antwort schließe ich daraus

  • obwohl dieses Konstrukt in der Grammatik einer Sprache trivial zu implementieren ist und Wert für Sprachbenutzer schafft,
  • der Hauptgrund dafür, dass dies in den meisten Sprachen nicht der Fall ist, liegt in seiner Bedeutung im Verhältnis zu anderen Merkmalen und in der mangelnden Bereitschaft der Leitungsgremien der Sprachen, entweder
    • verärgern Benutzer mit potenziell brechenden Änderungen
    • zu bewegen, um die Funktion zu implementieren (d. h.: Faulheit).

Einführung

Ich kann aus der Sicht eines Pythonisten zu dieser Frage sprechen. Ich bin Benutzer einer Sprache mit dieser Funktion und möchte die Implementierungsdetails der Sprache studieren. Darüber hinaus bin ich mit dem Prozess des Änderns von Sprachen wie C und C++ vertraut (der ISO-Standard wird vom Komitee geregelt und nach Jahr versioniert), und ich habe beobachtet, wie sowohl Ruby als auch Python wichtige Änderungen implementiert haben .

Dokumentation und Implementierung von Python

Aus den Dokumenten/der Grammatik geht hervor, dass wir eine beliebige Anzahl von Ausdrücken mit Vergleichsoperatoren verketten können:

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

und in der Dokumentation heißt es weiter:

Vergleiche können beliebig verkettet werden, z. B. ist x <y <= z äquivalent zu x <y und y <= z, außer dass y nur einmal ausgewertet wird (aber in beiden Fällen wird z überhaupt nicht ausgewertet, wenn x <y gefunden wird falsch sein).

Logische Äquivalenz

Damit

result = (x < y <= z)

ist logisch äquivalent in Bezug auf die Bewertung von x, y und z mit dem Ausnahme, dass y zweimal ausgewertet wird:

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

Der Unterschied besteht wiederum darin, dass y nur einmal mit (x < y <= z) Ausgewertet wird.

(Beachten Sie, dass die Klammern völlig unnötig und redundant sind, aber ich habe sie zum Nutzen derjenigen verwendet, die aus anderen Sprachen stammen, und der obige Code ist rechtmäßig Python.)

Überprüfen des analysierten abstrakten Syntaxbaums

Wir können untersuchen, wie Python verkettete Vergleichsoperatoren analysiert:

>>> 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')]))])"

Wir können also sehen, dass dies für Python oder eine andere Sprache nicht schwer zu analysieren ist.

>>> 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')]))])"

Und im Gegensatz zu der derzeit akzeptierten Antwort ist die ternäre Operation eine generische Vergleichsoperation, bei der der erste Ausdruck, eine Iterable spezifischer Vergleiche und eine Iterable von Ausdrucksknoten zur Bewertung nach Bedarf verwendet werden. Einfach.

Fazit zu Python

Ich persönlich finde die Bereichssemantik recht elegant, und die meisten Python - Fachleute, die ich kenne, würden die Verwendung der Funktion fördern, anstatt sie als schädlich zu betrachten - die Semantik ist in der renommierten Dokumentation (wie bereits erwähnt) ganz klar angegeben über).

Beachten Sie, dass Code viel mehr gelesen als geschrieben wird. Änderungen, die die Lesbarkeit von Code verbessern, sollten berücksichtigt und nicht durch das Hervorrufen allgemeiner Gespenster von Angst, Unsicherheit und Zweifel ausgeschlossen werden.

Warum ist x <y <z in Programmiersprachen nicht allgemein verfügbar?

Ich denke, es gibt einen Zusammenfluss von Gründen, die sich um die relative Bedeutung des Merkmals und die relative Dynamik/Trägheit des Wandels drehen, die von den Gouverneuren der Sprachen zugelassen wird.

Ähnliche Fragen können zu anderen wichtigeren Sprachfunktionen gestellt werden

Warum ist in Java oder C # keine Mehrfachvererbung verfügbar? Auf beide Fragen gibt es hier keine gute Antwort. Vielleicht waren die Entwickler zu faul, wie Bob Martin behauptet, und die angegebenen Gründe sind nur Ausreden. Und Mehrfachvererbung ist ein ziemlich großes Thema in der Informatik. Es ist sicherlich wichtiger als die Verkettung des Bedieners.

Es gibt einfache Problemumgehungen

Die Verkettung von Vergleichsoperatoren ist elegant, aber keineswegs so wichtig wie die Mehrfachvererbung. Und genau wie Java und C # Schnittstellen als Problemumgehung haben, funktioniert auch jede Sprache für mehrere Vergleiche - Sie verketten die Vergleiche einfach mit booleschen "und" s, was leicht genug funktioniert.

Die meisten Sprachen werden vom Ausschuss geregelt

Die meisten Sprachen werden vom Komitee weiterentwickelt (anstatt einen vernünftigen wohlwollenden Diktator fürs Leben zu haben, wie es Python getan hat). Und ich spekuliere, dass dieses Thema einfach nicht genug Unterstützung gefunden hat, um es aus den jeweiligen Ausschüssen herauszuholen.

Können sich die Sprachen, die diese Funktion nicht anbieten, ändern?

Wenn eine Sprache x < y < z Ohne die erwartete mathematische Semantik zulässt, wäre dies eine bahnbrechende Änderung. Wenn es das überhaupt nicht zuließ, wäre es fast trivial, es hinzuzufügen.

Änderungen brechen

In Bezug auf die Sprachen mit fehlerhaften Änderungen: Wir aktualisieren Sprachen mit Änderungen des Verhaltens, aber Benutzer mögen dies normalerweise nicht, insbesondere Benutzer von Funktionen, die möglicherweise fehlerhaft sind. Wenn sich ein Benutzer auf das frühere Verhalten von x < y < z Verlässt, würde er wahrscheinlich lautstark protestieren. Und da die meisten Sprachen vom Ausschuss regiert werden, bezweifle ich, dass wir viel politischen Willen bekommen würden, eine solche Änderung zu unterstützen.

37
Aaron Hall

Computersprachen versuchen, die kleinstmöglichen Einheiten zu definieren und sie kombinieren zu lassen. Die kleinstmögliche Einheit wäre so etwas wie "x <y", was ein boolesches Ergebnis ergibt.

Sie können nach einem ternären Operator fragen. Ein Beispiel wäre x <y <z. Welche Kombinationen von Operatoren erlauben wir nun? Offensichtlich sollte x> y> z oder x> = y> = z oder x> y> = z oder vielleicht x == y == z erlaubt sein. Was ist mit x <y> z? x! = y! = z? Was bedeutet der letzte, x! = Y und y! = Z oder dass alle drei unterschiedlich sind?

Jetzt Argumentwerbung: In C oder C++ werden Argumente zu einem gemeinsamen Typ heraufgestuft. Was bedeutet x <y <z für x ist doppelt, aber y und z sind lang lang int? Alle drei zum Doppel befördert? Oder wird y einmal doppelt und das andere Mal so lange genommen? Was passiert, wenn in C++ einer oder beide Operatoren überladen sind?

Und zuletzt erlauben Sie eine beliebige Anzahl von Operanden? Wie ein <b> c <d> e <f> g?

Nun, es wird alles sehr kompliziert. Was mir jetzt nichts ausmacht, ist, dass x <y <z einen Syntaxfehler erzeugt. Weil der Nutzen davon gering ist im Vergleich zu dem Schaden, der Anfängern zugefügt wird, die nicht herausfinden können, was x <y <z tatsächlich tut.

13
gnasher729

In vielen Programmiersprachen x < y ist ein binärer Ausdruck, der zwei Operanden akzeptiert und zu einem einzigen booleschen Ergebnis ausgewertet wird. Wenn Sie also mehrere Ausdrücke verketten, true < z und false < z macht keinen Sinn, und wenn diese Ausdrücke erfolgreich ausgewertet werden, führen sie wahrscheinlich zu einem falschen Ergebnis.

Es ist viel einfacher, an x < y als Funktionsaufruf , der zwei Parameter akzeptiert und ein einzelnes boolesches Ergebnis erzeugt. So viele Sprachen implementieren es tatsächlich unter der Haube. Es ist komponierbar, leicht kompilierbar und funktioniert einfach.

Das x < y < z Szenario ist viel komplizierter. Jetzt muss der Compiler tatsächlich drei Funktionen erstellen: x < y, y < z und das Ergebnis dieser beiden Werte zusammen, alle im Kontext einer wohl mehrdeutigen Sprachgrammatik .

Warum haben sie es anders gemacht? Weil es eine eindeutige Grammatik ist, viel einfacher zu implementieren und viel einfacher zu korrigieren ist.

10
Robert Harvey

Die meisten Mainstream-Sprachen sind (zumindest teilweise) objektorientiert. Grundsätzlich besteht das zugrunde liegende Prinzip von OO) darin, dass Objekte Nachrichten an andere Objekte (oder an sich selbst) und an den Empfänger senden. dieser Nachricht hat die vollständige Kontrolle darüber, wie auf diese Nachricht reagiert werden soll.

Nun wollen wir sehen, wie wir so etwas implementieren würden

a < b < c

Wir könnten es streng von links nach rechts bewerten (linksassoziativ):

a.__lt__(b).__lt__(c)

Aber jetzt rufen wir __lt__ Für das Ergebnis von a.__lt__(b) auf, das ein Boolean ist. Das macht keinen Sinn.

Versuchen wir es mit rechtsassoziativ:

a.__lt__(b.__lt__(c))

Nein, das macht auch keinen Sinn. Jetzt haben wir a < (something that's a Boolean).

Okay, was ist mit der Behandlung als syntaktischer Zucker? Lassen Sie uns eine Kette von n < Vergleichen, die eine n-1-ary Nachricht senden. Dies könnte bedeuten, dass wir die Nachricht __lt__ An a senden und b und c als Argumente übergeben:

a.__lt__(b, c)

Okay, das funktioniert, aber hier gibt es eine seltsame Asymmetrie: a kann entscheiden, ob es kleiner als b ist. Aber b kann nicht entscheiden, ob es kleiner als c ist, stattdessen wird diese Entscheidung auch von getroffen a.

Was ist mit der Interpretation als n-ary Nachricht, die an this gesendet wird?

this.__lt__(a, b, c)

Schließlich! Das kann funktionieren. Dies bedeutet jedoch, dass die Reihenfolge der Objekte keine Eigenschaft des Objekts mehr ist (z. B. ob a kleiner als b ist, ist weder eine Eigenschaft von a noch von b) sondern eine Eigenschaft des Kontexts (dh this).

Vom Mainstream-Standpunkt aus scheint das seltsam. Z.B. in Haskell ist das normal. Es kann beispielsweise mehrere verschiedene Implementierungen der Typklasse Ord geben, und ob a kleiner als b ist oder nicht, hängt davon ab, welche Typklasseninstanz sich zufällig im Gültigkeitsbereich befindet.

Aber eigentlich ist es überhaupt nicht komisch! Sowohl Java ( Comparator ) als auch .NET ( IComparer ) verfügen über Schnittstellen, die dies ermöglichen Fügen Sie Ihre eigene Ordnungsbeziehung in z. B. Sortieralgorithmen ein. Sie erkennen somit voll und ganz an, dass eine Reihenfolge nicht an einen Typ gebunden ist, sondern vom Kontext abhängt.

Soweit ich weiß, gibt es derzeit keine Sprachen, die eine solche Übersetzung durchführen. Es gibt jedoch einen Vorrang: Beide Ioke und Seph haben das, was ihr Designer "trinäre Operatoren" nennt - Operatoren, die syntaktisch sind binär, aber semantisch ternär. Bestimmtes,

a = b

wird nicht so interpretiert, dass die Nachricht = an a gesendet wird, wobei b als Argument übergeben wird, sondern als Senden der Nachricht = an den "aktuellen Grund" (ein Konzept ähnlich, aber nicht identisch mit this), wobei a und b als Argumente übergeben werden. Also wird a = b Als interpretiert

=(a, b)

und nicht

a =(b)

Dies könnte leicht auf n-ary-Operatoren verallgemeinert werden.

Beachten Sie, dass dies für OO Sprachen) wirklich eigen ist. In OO haben wir immer ein einziges Objekt, das letztendlich für die Interpretation eines gesendeten Nachrichtens verantwortlich ist, und wie wir gesehen haben, ist dies für nicht sofort offensichtlich so etwas wie a < b < c welches Objekt das sein soll.

Dies gilt jedoch nicht für prozedurale oder funktionale Sprachen. Zum Beispiel ist in Schema , Common LISP und Clojure die Funktion < N-ary und kann aufgerufen werden mit einer beliebigen Anzahl von Argumenten.

Insbesondere bedeutet < nicht "weniger als", sondern diese Funktionen werden etwas anders interpretiert:

(<  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

Es ist einfach so, weil die Sprachdesigner nicht daran gedacht haben oder es nicht für eine gute Idee gehalten haben. Python macht es so, wie Sie es mit einer einfachen (fast) LL (1) Grammatik beschrieben haben.

3
Neil G

Das folgende C++ - Programm kompiliert mit keinem Blick von clang, selbst wenn Warnungen auf die höchstmögliche Stufe gesetzt sind (-Weverything):

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

Die Gnu-Compiler-Suite hingegen warnt mich freundlich davor, dass comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses].

Meine Frage lautet also: Warum ist x <y <z in Programmiersprachen mit der erwarteten Semantik nicht allgemein verfügbar?

Die Antwort ist einfach: Abwärtskompatibilität. Es gibt eine große Menge an Code in freier Wildbahn, die das Äquivalent von 1<3<2 Verwenden und erwarten, dass das Ergebnis wahr ist.

Ein Sprachdesigner hat nur eine Chance, dies "richtig" zu machen, und das ist der Zeitpunkt, an dem die Sprache zum ersten Mal entworfen wird. "Falsch" zu verstehen bedeutet zunächst, dass andere Programmierer dieses "falsche" Verhalten ziemlich schnell ausnutzen. Wenn Sie es beim zweiten Mal "richtig" machen, wird diese vorhandene Codebasis beschädigt.

2
David Hammen