Wenn ich zwei Zahlen (oder andere geordnete Entitäten) vergleichen möchte, würde ich dies mit tun x < y
. Wenn ich drei davon vergleichen möchte, schlägt der Algebra-Schüler vor, es zu versuchen x < y < z
. Der Programmierer in mir antwortet dann mit "nein, das ist nicht gültig, das müssen Sie tun x < y && y < z
".
Die meisten Sprachen, auf die ich gestoßen bin, scheinen diese Syntax nicht zu unterstützen, was seltsam ist, da sie in der Mathematik häufig vorkommt. Python ist eine bemerkenswerte Ausnahme. JavaScript scheint eine Ausnahme zu sein, ist jedoch nur ein unglückliches Nebenprodukt der Operatorpräzision und impliziten Konvertierungen. in node.js, 1 < 3 < 2
ausgewertet true
, weil es wirklich ist (1 < 3) < 2 === true < 2 === 1 < 2
.
Meine Frage lautet also: Warum ist x < y < z
es in Programmiersprachen nicht allgemein verfügbar, mit der erwarteten Semantik?
static bool IsInRange<T>(this T candidate, T lower, T upper) where T : IComparable<T>
wenn es Sie wirklich stört,&&
s zu sehen )Antworten:
Dies sind binäre Operatoren, die, wenn sie verkettet sind, normalerweise und natürlich einen abstrakten Syntaxbaum erzeugen wie:
Wenn ausgewertet (was Sie von Anfang an tun), ergibt dies ein boolesches Ergebnis
x < y
, woraufhin Sie einen Tippfehler erhalten , der versucht, dies zu tunboolean < z
. Damitx < y < z
Sie wie beschrieben arbeiten können, müssen Sie im Compiler einen speziellen Fall erstellen, um einen Syntaxbaum wie den folgenden zu erstellen:Nicht, dass das nicht möglich wäre. Es ist offensichtlich, aber es fügt dem Parser eine gewisse Komplexität hinzu für einen Fall, der nicht wirklich so oft auftritt. Sie erstellen im Grunde genommen ein Symbol, das manchmal wie ein binärer Operator und manchmal wie ein ternärer Operator wirkt, mit allen Auswirkungen der Fehlerbehandlung und den damit verbundenen. Das schafft viel Raum für Fehlentwicklungen, die Sprachdesigner nach Möglichkeit lieber vermeiden würden.
quelle
x<y<z
bedeutet, oder was noch wichtiger istx<y<=z
. Diese Antwort wirdx<y<z
als trinärer Operator interpretiert . Genau so sollte dieser gut definierte mathematische Ausdruck nicht interpretiert werden.x<y<z
ist stattdessen eine Abkürzung für(x<y)&&(y<z)
. Die einzelnen Vergleiche sind immer noch binär.Warum ist
x < y < z
es in Programmiersprachen nicht allgemein verfügbar?Daraus schließe ich
Einführung
Ich kann aus der Perspektive eines Pythonisten zu dieser Frage sprechen. Ich bin ein Benutzer einer Sprache mit dieser Funktion und möchte die Implementierungsdetails der Sprache studieren. Darüber hinaus bin ich ein wenig mit dem Prozess des Wechsels von Sprachen wie C und C ++ vertraut (der ISO-Standard wird vom Komitee geregelt und nach Jahr versioniert), und ich habe sowohl Ruby als auch Python dabei beobachtet, wie sie wichtige Änderungen umsetzen.
Dokumentation und Implementierung von Python
In der Dokumentation / Grammatik sehen wir, dass wir eine beliebige Anzahl von Ausdrücken mit Vergleichsoperatoren verketten können:
und in der Dokumentation heißt es weiter:
Logische Äquivalenz
So
ist logisch äquivalent in Bezug auf die Auswertung von
x
,y
undz
, mit der Ausnahme, dassy
zweimal ausgewertet wird:Auch hier besteht der Unterschied darin, dass y nur einmal mit ausgewertet wird
(x < y <= z)
.(Beachten Sie, dass die Klammern völlig unnötig und überflüssig sind, aber ich habe sie zum Nutzen derjenigen verwendet, die aus anderen Sprachen stammen, und der obige Code ist rechtmäßig für Python.)
Untersuchen des analysierten abstrakten Syntaxbaums
Wir können untersuchen, wie Python verkettete Vergleichsoperatoren analysiert:
Wir können also sehen, dass es für Python oder eine andere Sprache nicht schwierig ist, das zu analysieren.
Und im Gegensatz zu der gegenwärtig akzeptierten Antwort ist die ternäre Operation eine generische Vergleichsoperation, die den ersten Ausdruck, eine Iteration spezifischer Vergleiche und eine Iteration von Ausdrucksknoten benötigt, um nach Bedarf ausgewertet zu werden. Einfach.
Fazit zu Python
Ich persönlich finde die Bereichssemantik recht elegant, und die meisten mir bekannten Python-Experten würden die Verwendung der Funktion fördern, anstatt sie als schädlich zu betrachten. Die Semantik wird in der bekannten Dokumentation (wie oben erwähnt) ganz klar angegeben.
Beachten Sie, dass Code viel mehr gelesen als geschrieben wird. Änderungen, die die Lesbarkeit von Code verbessern, sollten aufgegriffen und nicht durch das Hervorrufen von allgemeinen Gespenstern von Angst, Unsicherheit und Zweifel außer Acht gelassen werden .
Warum ist x <y <z in Programmiersprachen nicht allgemein verfügbar?
Ich denke, es gibt eine Zusammenführung von Gründen, die sich um die relative Bedeutung des Merkmals und den relativen Impuls / die Trägheit der Änderung drehen, die die Gouverneure der Sprachen zulassen.
Ähnliche Fragen können zu anderen, wichtigeren Sprachmerkmalen gestellt werden
Warum ist Mehrfachvererbung in Java oder C # nicht 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 lediglich Ausreden. Und Mehrfachvererbung ist ein ziemlich großes Thema in der Informatik. Es ist sicherlich wichtiger als die Verkettung von Operatoren.
Es gibt einfache Problemumgehungen
Die Verkettung von Vergleichsoperatoren ist elegant, aber keineswegs so wichtig wie die Mehrfachvererbung. Und so wie Java und C # Schnittstellen als Workaround haben, so hat auch jede Sprache 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 entwickelt (anstatt wie Python einen vernünftigen gütigen Diktator für das Leben zu haben). Und ich spekuliere, dass dieses Thema einfach nicht genug Unterstützung fand, um es aus den jeweiligen Ausschüssen herauszuholen.
Können sich die Sprachen ändern, die diese Funktion nicht anbieten?
Wenn eine Sprache
x < y < z
ohne die erwartete mathematische Semantik auskommt, wäre dies eine bahnbrechende Veränderung. Wenn es das überhaupt nicht zulassen würde, wäre es fast trivial hinzuzufügen.Veränderungen brechen
In Bezug auf die Sprachen mit aktuellen Änderungen: Wir aktualisieren Sprachen mit aktuellen Verhaltensänderungen, aber Benutzer mögen dies in der Regel nicht, insbesondere Benutzer von Funktionen, die möglicherweise fehlerhaft sind. Wenn sich ein Benutzer auf das frühere Verhalten von verlässt
x < y < z
, würde er wahrscheinlich lautstark protestieren. Und da die meisten Sprachen von Komitees verwaltet werden, bezweifle ich, dass wir viel politischen Willen bekommen würden, eine solche Änderung zu unterstützen.quelle
x < y < z
zu(x < y) && (y < z)
. Das mentale Modell für den verketteten Vergleich ist die allgemeine Mathematik. Der klassische Vergleich ist nicht die Mathematik im Allgemeinen, sondern die Boolesche Logik.x < y
erzeugt eine binäre Antwort{0}
.y < z
erzeugt eine binäre Antwort{1}
.{0} && {1}
erzeugt die beschreibende Antwort. Die Logik ist zusammengesetzt und nicht naiv verkettet.x<y<z
. Eine Sprache hat einmal die Chance, so etwas richtig zu machen, und diese eine Chance steht am Anfang der Sprache.I have watched both Ruby and Python implement breaking changes.
Für diejenigen, die neugierig sind, ist hier eine bahnbrechende Änderung in C # 5.0, die Schleifenvariablen und -schließungenComputersprachen versuchen, die kleinstmöglichen Einheiten zu definieren und Sie können sie kombinieren. 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 verschieden sind?
Jetzt Argument-Promotions: In C oder C ++ würden Argumente zu einem gemeinsamen Typ heraufgestuft. Also, was bedeutet x <y <z für x ist doppelt, aber y und z sind long long int? Alle drei befördert, um sich zu verdoppeln? Oder wird y einmal und in der anderen Zeit so lange wie doppelt genommen? Was passiert, wenn in C ++ einer oder beide Operatoren überladen sind?
Und erlauben Sie zuletzt 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 im Vergleich zu dem Schaden, der Anfängern zugefügt wird, die nicht herausfinden können, was x <y <z tatsächlich tut, gering ist.
quelle
x + y + z
, mit dem einzigen Unterschied, dass dies keinen semantischen Unterschied impliziert. Es ist nur so, dass es keiner bekannten Sprache jemals daran gelegen hat.x < y < z
das entspricht,((x < y) and (y < z))
abery
nur einmal ausgewertet wird ), und ich würde mir vorstellen, dass kompilierte Sprachen ihren Weg optimieren. "Weil der Nutzen davon im Vergleich zu dem Schaden, der Anfängern zugefügt wird, die nicht herausfinden können, was x <y <z tatsächlich tut, gering ist." Ich finde es unglaublich nützlich. Wahrscheinlich werde ich dafür -1 ...a < b > c < d > e < f > g
, mit der "offensichtlichen" Bedeutung(a < b) and (b > c) and (c < d) and (d > e) and (e < f) and (f > g)
. Nur weil Sie schreiben können, heißt das nicht, dass Sie es sollten. Das Eliminieren solcher Monstrositäten ist das Ziel der Codeüberprüfung. Andererseits hat das Schreiben0 < x < 8
in Python die offensichtliche Bedeutung (keine Anführungszeichen), dass x exklusiv zwischen 0 und 8 liegt.In vielen Programmiersprachen
x < y
ist ein binärer Ausdruck, der zwei Operanden akzeptiert und zu einem einzigen booleschen Ergebnis ausgewertet wird. Wenn also mehrere Ausdrücke verketten,true < z
undfalse < z
wird keinen Sinn machen, und wenn diese Ausdrücke erfolgreich bewerten, sind sie wahrscheinlich das falsche Ergebnis zu produzieren.Es ist viel einfacher, sich
x < y
einen Funktionsaufruf vorzustellen , der zwei Parameter verwendet und ein einziges boolesches Ergebnis erzeugt. Genau so viele Sprachen setzen es unter der Haube um. Es ist kompilierbar, einfach zu kompilieren und funktioniert einfach.Das
x < y < z
Szenario ist viel komplizierter. Jetzt ist der Compiler in der Tat hat die Mode drei Funktionen:x < y
,y < z
und das Ergebnis dieser beiden Werte anded zusammen, die alle im Rahmen einer wohl mehrdeutigen Sprache Grammatik .Warum haben sie es anders gemacht? Weil es eine eindeutige Grammatik ist, viel einfacher zu implementieren und viel einfacher zu korrigieren ist.
quelle
e1 op1 e2 op2 e3 op3 ...
äquivalent ist, mit dere1 op e2 and e2 op2 e3 and ...
Ausnahme, dass jeder Ausdruck nur einmal ausgewertet wird. (Übrigens hat diese einfache Regel die verwirrende Nebenwirkung, dass Aussagen wiea == b is True
nicht mehr die beabsichtigte Wirkung haben.)re:answer
Dies war der Punkt, an dem ich mich sofort für meinen Kommentar zur Hauptfrage entschieden habe. Ich halte es nicht für angebrachtx < y < z
, der Sprachsemantik einen bestimmten Wert beizumessen.(x < y) && (y < z)
wird breiter gestützt, ist expliziter, ausdrucksvoller, leichter in seine Bestandteile zu zerlegen, komponierbarer, logischer, leichter umgestaltbar.Die meisten gängigen Sprachen sind (zumindest teilweise) objektorientiert. Grundsätzlich besteht das Grundprinzip von OO darin, dass Objekte Nachrichten an andere Objekte (oder an sich selbst) senden und der Empfänger dieser Nachricht die vollständige Kontrolle darüber hat, wie auf diese Nachricht zu antworten ist.
Nun wollen wir sehen, wie wir so etwas implementieren würden
Wir könnten es streng von links nach rechts auswerten (links-assoziativ):
Aber jetzt rufen wir
__lt__
das Ergebnis von aufa.__lt__(b)
, das a istBoolean
. Das macht keinen Sinn.Versuchen wir mal rechtsassoziativ:
Nein, das ergibt auch keinen Sinn. Jetzt haben wir
a < (something that's a Boolean)
.Okay, wie wäre es mit syntaktischem Zucker? Lassen Sie uns eine Kette von n
<
Vergleichen erstellen, die eine n-1-fache Nachricht senden. Dies könnte bedeuten, wir senden Sie die Nachricht__lt__
ana
, vorbeib
undc
als Argument:Okay, das funktioniert, aber hier gibt es eine merkwürdige Asymmetrie:
a
Man muss sich entscheiden, ob es weniger ist alsb
. Aberb
ich kann mich nicht entscheiden, ob es kleiner ist alsc
, stattdessen wird diese Entscheidung auch von getroffena
.Was ist mit der Interpretation als n-fache Nachricht, an die gesendet wird
this
?Endlich! Das kann funktionieren. Dies bedeutet jedoch, dass die Anordnung von Objekten nicht länger eine Eigenschaft des Objekts ist (z. B. ob
a
kleiner alsb
weder eine Eigenschaft vona
noch von istb
), sondern eine Eigenschaft des Kontexts (dhthis
).Aus der Sicht des Mainstreams scheint das komisch. Das ist aber zB in Haskell normal. Beispielsweise kann es mehrere verschiedene Implementierungen der
Ord
Typenklasse geben, und ob diesea
kleiner alsb
ist oder nicht , hängt davon ab, welche Typenklasseninstanz sich gerade im Gültigkeitsbereich befindet.Aber eigentlich ist es gar nicht so komisch! Sowohl Java (
Comparator
) als auch .NET (IComparer
) verfügen über Schnittstellen, mit denen Sie Ihre eigene Ordnungsrelation in z. B. Sortieralgorithmen einfügen können. Sie erkennen daher voll und ganz an, dass eine Bestellung 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: Sowohl Ioke als auch Seph haben sogenannte "Trinary Operators" - Operatoren, die syntaktisch binär, aber semantisch ternär sind. Bestimmtes,
wird nicht so interpretiert, dass die Nachricht als Argument
=
an diea
Übergabe gesendet wirdb
, sondern als dass die Nachricht=
an die "aktuelle Basis" gesendet wird (ein Konzept, das demthis
Übergeben ähnlich, aber nicht identisch ist )a
undb
als Argumente. Soa = b
wird interpretiert alsund nicht
Dies könnte leicht auf n-fache Operatoren verallgemeinert werden.
Beachten Sie, dass dies für OO-Sprachen sehr eigen ist. In OO haben wir immer ein einziges Objekt, das letztendlich für die Interpretation eines Nachrichtensends verantwortlich ist, und wie wir gesehen haben, ist es für so etwas wie
a < b < c
das Objekt, das sein sollte, nicht sofort offensichtlich .Dies gilt jedoch nicht für prozedurale oder funktionale Sprachen. In Scheme , Common Lisp und Clojure ist die
<
Funktion beispielsweise n-ary und kann mit einer beliebigen Anzahl von Argumenten aufgerufen werden.Insbesondere
<
bedeutet nicht "kleiner als", sondern diese Funktionen werden etwas anders interpretiert:quelle
Es liegt einfach daran, dass die Sprachdesigner nicht daran gedacht haben oder es nicht für eine gute Idee hielten. Python macht es so, wie Sie es beschrieben haben, mit einer einfachen (fast) LL (1) -Grammatik.
quelle
1 < 2 < 3
Java oder C # verwenden, haben Sie kein Problem mit der Rangfolge der Operatoren. Sie haben ein Problem mit ungültigen Typen. Das Problem ist, dass dies immer noch genau so analysiert wird, wie Sie es geschrieben haben, aber Sie benötigen eine Spezialfalllogik im Compiler, um es von einer Folge einzelner Vergleiche in einen verketteten Vergleich umzuwandeln.Das folgende C ++ - Programm kompiliert mit nary a peep from clang, auch wenn Warnungen auf die höchstmögliche Stufe gesetzt sind (
-Weverything
):Die Gnu-Compiler-Suite hingegen warnt mich freundlich davor
comparisons like 'X<=Y<=Z' do not have their mathematical meaning [-Wparentheses]
.Die Antwort ist einfach: Abwärtskompatibilität. Es gibt eine Unmenge von Code in freier Wildbahn, der das Äquivalent von verwendet
1<3<2
und erwartet, 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. Verstehen Sie es "falsch" bedeutet zunächst, dass andere Programmierer dieses "falsche" Verhalten ziemlich schnell ausnutzen. Wenn Sie es beim zweiten Mal "richtig" machen, wird die vorhandene Codebasis zerstört.
quelle
if x == y is True : ...
: Meiner Meinung nach verdienen Menschen, die solche Codes schreiben, eine ganz besondere, außergewöhnliche Folter, die Torquemada selbst in Ohnmacht fallen lässt (wenn er jetzt lebt).