Warum sind benutzerdefinierte Operatoren nicht häufiger?

94

Eine Funktion, die ich in funktionalen Sprachen vermisse, ist die Idee, dass Operatoren nur Funktionen sind. Das Hinzufügen eines benutzerdefinierten Operators ist daher oft so einfach wie das Hinzufügen einer Funktion. Viele prozedurale Sprachen erlauben Überladungen von Operatoren, so dass Operatoren in gewissem Sinne immer noch Funktionen sind (dies gilt sehr für D, wo der Operator als Zeichenfolge in einem Vorlagenparameter übergeben wird).

Wo das Überladen von Operatoren zulässig ist, scheint es oft trivial zu sein, zusätzliche benutzerdefinierte Operatoren hinzuzufügen. Ich habe diesen Blog-Beitrag gefunden , der argumentiert, dass benutzerdefinierte Operatoren aufgrund von Vorrangregeln nicht gut mit der Infix-Notation funktionieren, aber der Autor gibt verschiedene Lösungen für dieses Problem an.

Ich habe mich umgesehen und konnte keine prozeduralen Sprachen finden, die benutzerdefinierte Operatoren in der Sprache unterstützen. Es gibt Hacks (wie Makros in C ++), aber das ist kaum dasselbe wie Sprachunterstützung.

Warum ist diese Funktion nicht häufiger anzutreffen, da sie ziemlich einfach zu implementieren ist?

Ich verstehe, dass dies zu hässlichem Code führen kann, aber das hat Sprachdesigner in der Vergangenheit nicht davon abgehalten, nützliche Funktionen hinzuzufügen, die leicht missbraucht werden können (Makros, ternäre Operatoren, unsichere Zeiger).

Tatsächliche Anwendungsfälle:

  • Fehlende Operatoren implementieren (zB Lua hat keine bitweisen Operatoren)
  • Mimic D's ~(Array-Verkettung)
  • DSLs
  • Verwendung |als Syntaxzucker im Unix-Pipe-Stil (mit Coroutinen / Generatoren)

Ich bin auch in Sprachen interessiert , die tun benutzerdefinierte Operatoren erlauben, aber ich bin mehr daran interessiert, warum es ausgeschlossen wurde. Ich dachte darüber nach, eine Skriptsprache zu forken, um benutzerdefinierte Operatoren hinzuzufügen, stoppte mich jedoch, als ich merkte, dass ich sie nirgendwo gesehen habe. Es gibt also wahrscheinlich einen guten Grund, warum Sprachdesigner, die schlauer sind als ich, es nicht zugelassen haben.

Beatgammit
quelle
4
Zu dieser Frage gibt es eine Reddit-Diskussion .
Dynamische
2
@dimatura: Ich weiß nicht viel über R, aber die benutzerdefinierten Operatoren scheinen immer noch nicht sehr flexibel zu sein (z. B. kann man Fixität, Umfang, Überladung usw. nicht richtig definieren), was erklärt, warum sie nicht oft verwendet werden. Das ist anders in anderen Sprachen, sicherlich in Haskell , die benutzerdefinierten Infixoperatoren verwendet eine große Menge . Ein weiteres Beispiel für eine prozedurale Sprache mit angemessener Unterstützung für benutzerdefinierte Infixe ist Nimrod , und natürlich lässt Perl dies auch zu .
links um ca. 30.12.12
4
Es gibt die andere Möglichkeit, Lisp hat tatsächlich keinen Unterschied zwischen Operatoren und Funktionen.
BeardedO
4
Noch keine Erwähnung von Prolog, einer anderen Sprache, in der Operatoren nur syntaktischer Zucker für Funktionen sind (ja sogar mathematische) und mit der Sie benutzerdefinierte Operatoren mit benutzerdefinierter Priorität definieren können.
David Cowden
2
@BeardedO, in Lisp gibt es überhaupt keine Infix-Operatoren. Sobald Sie sie eingeführt haben, müssen Sie sich vorrangig mit allen Fragen und dergleichen befassen.
SK-logic

Antworten:

134

Beim Design von Programmiersprachen gibt es zwei diametral entgegengesetzte Denkrichtungen. Zum einen schreiben Programmierer besseren Code mit weniger Einschränkungen, zum anderen schreiben sie besseren Code mit mehr Einschränkungen. Meiner Meinung nach ist die Realität, dass gute erfahrene Programmierer mit weniger Einschränkungen gedeihen, diese Einschränkungen jedoch der Codequalität von Anfängern zugute kommen können.

Benutzerdefinierte Operatoren können für sehr eleganten Code in erfahrenen Händen sorgen und für einen Anfänger einen absolut schrecklichen Code. Ob Ihre Sprache sie enthält oder nicht, hängt also von der Denkweise Ihres Sprachdesigners ab.

Karl Bielefeldt
quelle
23
Plus.google.com/110981030061712822816/posts/KaSKeg4vQtz gibt in beiden Fällen auch +1 für die direkteste (und wahrscheinlich zutreffendste) Antwort auf die Frage, warum sich Sprachdesigner für eine andere entscheiden. Ich würde auch sagen, dass Sie aufgrund Ihrer These extrapolieren könnten, dass weniger Sprachen dies zulassen, weil ein kleinerer Teil der Entwickler hochqualifiziert ist als sonst (der höchste Prozentsatz von allem wird per Definition immer eine Minderheit sein)
Jimmy Hoffa
12
Ich denke, diese Antwort ist vage und nur am Rande mit der Frage verbunden. (Ich denke auch, dass Ihre Charakterisierung falsch ist, da sie meiner Erfahrung widerspricht, aber das ist nicht wichtig.)
Konrad Rudolph
1
Wie @KonradRudolph sagte, beantwortet dies die Frage nicht wirklich. Nehmen Sie eine Sprache wie Prolog, mit der Sie mit Operatoren alles tun können, was Sie wollen, einschließlich der Festlegung ihrer Priorität, wenn sie als Infix oder Präfix platziert werden. Ich denke nicht, dass die Tatsache, dass Sie benutzerdefinierte Operatoren schreiben können, etwas mit dem Kenntnisstand der Zielgruppe zu tun hat, sondern vielmehr, weil das Ziel von Prolog darin besteht, so logisch wie möglich zu lesen. Durch die Einbeziehung benutzerdefinierter Operatoren können Sie Programme schreiben, die sehr logisch lesen (schließlich besteht ein Prolog-Programm nur aus einer Reihe logischer Anweisungen).
David Cowden
Wie habe ich diesen Stevey Rant verpasst? Oh man, mit einem Lesezeichen versehen
George Mauer
In welche Philosophie würde ich fallen, wenn ich denke, dass Programmierer am wahrscheinlichsten den besten Code schreiben, wenn Sprachen ihnen die Werkzeuge geben, um zu sagen, wann der Compiler verschiedene Schlussfolgerungen ziehen sollte (z. B. Auto-Boxing-Argumente für eine FormatMethode) und wann er ablehnen sollte ( zB Auto-Boxing Argumente zu ReferenceEquals). Je besser Programmierer in der Lage sind, zu sagen, wann bestimmte Schlussfolgerungen unangemessen sind, desto sicherer können geeignete Schlussfolgerungen getroffen werden.
Supercat
83

Bei der Wahl zwischen der Verkettung von Arrays mit ~ oder mit "myArray.Concat (secondArray)" würde ich wahrscheinlich letzteres vorziehen. Warum? Weil ~ ein völlig bedeutungsloses Zeichen ist, dessen Bedeutung - die der Array-Verkettung - nur in dem spezifischen Projekt angegeben wird, in dem es geschrieben wurde.

Grundsätzlich unterscheiden sich Operatoren, wie Sie sagten, nicht von Methoden. Während Methoden mit lesbaren, verständlichen Namen versehen werden können, die das Verständnis des Codeflusses verbessern, sind Operatoren undurchsichtig und situationsbezogen.

Aus diesem Grund mag ich auch nicht den PHP- .Operator (String-Verkettung) oder die meisten Operatoren in Haskell oder OCaml, obwohl in diesem Fall einige allgemein akzeptierte Standards für funktionale Sprachen auftauchen.

Avner Shahar-Kashtan
quelle
27
Das Gleiche gilt für alle Betreiber. Was sie gut macht und auch benutzerdefinierte Operatoren gut machen kann (wenn sie vernünftig definiert sind), ist Folgendes: Obwohl das für sie verwendete Zeichen nur ein Zeichen ist, machen die weit verbreitete Verwendung und möglicherweise mnemonische Eigenschaften seine Bedeutung im Quellcode sofort geltend offensichtlich für diejenigen, die damit vertraut sind. Ihre Antwort ist meiner Meinung nach also nicht so überzeugend.
33
Wie ich am Ende erwähnt habe, haben einige Operatoren eine universelle Erkennbarkeit (wie z. B. die üblichen arithmentischen Symbole, bitweise Verschiebungen, UND / ODER usw.), und daher überwiegt ihre Kürze ihre Undurchsichtigkeit. Es scheint jedoch das Schlimmste beider Welten zu sein, willkürliche Operatoren definieren zu können .
Avner Shahar-Kashtan
11
Sie sind also auch dafür, beispielsweise Namen mit einem Buchstaben auf Sprachebene zu verbieten? Generell nichts in der Sprache zuzulassen, was mehr schaden als nützen könnte? Das ist eine gültige Herangehensweise an Sprachdesign, aber nicht die einzige. Ich glaube nicht, dass Sie dies nicht voraussetzen können.
9
Ich bin nicht damit einverstanden, dass benutzerdefinierte Operatoren eine Funktion "hinzufügen", sondern eine Einschränkung aufheben. Operatoren sind im Allgemeinen nur Funktionen, sodass anstelle einer statischen Symboltabelle für Operatoren eine dynamische, kontextabhängige Tabelle verwendet werden kann. Ich stelle mir vor, dass so mit Überladungen von Operatoren umgegangen wird, weil +und mit <<Sicherheit nicht definiert Object(wenn ich das in einer nackten Klasse in C ++ mache, bekomme ich "keine Übereinstimmung für Operator + in ...").
Beatgammit
10
Es dauert lange, um herauszufinden, dass es beim Codieren normalerweise nicht darum geht, einen Computer dazu zu bringen, etwas für Sie zu tun. Es geht darum, ein Dokument zu erstellen, das sowohl von Menschen als auch von Computern gelesen werden kann. Diese Antwort ist genau richtig. Wenn die nächste Person (oder Sie in 2 Jahren) noch 10 Sekunden damit verbringen muss, herauszufinden, was ~ bedeutet, hätten Sie stattdessen auch die 10 Sekunden damit verbracht, einen Methodenaufruf einzugeben.
Bill K
71

Warum ist diese Funktion nicht häufiger anzutreffen, da sie ziemlich einfach zu implementieren ist?

Ihre Prämisse ist falsch. Es ist nicht "ziemlich trivial zu implementieren". In der Tat bringt es eine Tüte Probleme.

Werfen wir einen Blick auf die vorgeschlagenen "Lösungen" in der Post:

  • Kein Vorrang . Der Autor selbst sagt: "Die Nichtverwendung von Vorrangregeln ist einfach keine Option."
  • Semantikbewusstes Parsen . Wie der Artikel sagt, würde dies erfordern, dass der Compiler über viel semantisches Wissen verfügt. Der Artikel bietet eigentlich keine Lösung dafür und ich sage Ihnen, das ist einfach nicht trivial. Compiler sind als Kompromiss zwischen Leistung und Komplexität konzipiert. Insbesondere erwähnt der Autor einen Voranalyseschritt, um die relevanten Informationen zu sammeln, aber das Voranalysieren ist ineffizient und die Compiler bemühen sich sehr, Parsing-Pässe zu minimieren.
  • Keine benutzerdefinierten Infix-Operatoren . Nun, das ist keine Lösung.
  • Hybridlösung . Diese Lösung birgt viele (aber nicht alle) Nachteile des semantikbewussten Parsings. Insbesondere, da der Compiler unbekannte Token als potenziell repräsentativ für benutzerdefinierte Operatoren behandeln muss, kann er häufig keine aussagekräftigen Fehlermeldungen erzeugen. Es kann auch erforderlich sein, dass die Definition des Operators mit dem Parsen fortfährt (um Typinformationen usw. zu sammeln), was wiederum einen zusätzlichen Parsing-Durchgang erforderlich macht.

Alles in allem ist dies eine teure Funktion, sowohl in Bezug auf die Komplexität der Parser als auch in Bezug auf die Leistung, und es ist nicht klar, dass dies viele Vorteile mit sich bringen würde. Sicher, die Fähigkeit, neue Operatoren zu definieren , hat einige Vorteile, aber selbst diese sind umstritten (sehen Sie sich einfach die anderen Antworten an, in denen argumentiert wird, dass neue Operatoren keine gute Sache sind).

Konrad Rudolph
quelle
2
Danke für die vernünftige Stimme. Meine Erfahrung zeigt, dass Leute, die Dinge als trivial abtun, entweder Experten oder selig unwissend sind, und häufiger in der letzteren Kategorie: x
Matthieu M.
14
Jedes einzelne dieser Probleme wurde in Sprachen gelöst, die es seit 20 Jahren gibt ...
Philip JF
11
Und doch ist es außerordentlich trivial zu implementieren. Vergessen Sie all diese Algorithmen zum Parsen von Drachenbüchern, es ist ein 21. Jahrhundert und es ist eine Zeit, weiterzumachen. Auch das Erweitern der Sprachsyntax selbst ist einfach, und das Hinzufügen von Operatoren einer bestimmten Priorität ist trivial. Sehen Sie sich zum Beispiel Haskell-Parser an. Es ist viel, viel einfacher als Parser für die "Mainstream" -Sprachen.
SK-logic
3
@ SK-logic Deine pauschale Aussage überzeugt mich nicht. Ja, das Parsen ist weitergegangen. Nein, die Implementierung beliebiger Operator-Prioritäten je nach Typ ist nicht „trivial“. Gute Fehlermeldungen mit begrenztem Kontext zu produzieren, ist nicht „trivial“. Das Erstellen effizienter Parser mit Sprachen, für deren Transformation mehrere Durchgänge erforderlich sind, ist nicht möglich.
Konrad Rudolph
6
In Haskell ist das Parsen "einfach" (im Vergleich zu den meisten Sprachen). Der Vorrang ist kontextunabhängig. Der Teil, der schwere Fehlermeldungen ausgibt, bezieht sich auf Typenklassen- und erweiterte Typensystemfunktionen, nicht auf benutzerdefinierte Operatoren. Es ist eine Überlastung, keine Benutzerdefinition, das ist das schwierige Problem.
Philip JF
25

Ignorieren wir zunächst das gesamte Argument "Operatoren werden missbraucht, um die Lesbarkeit zu beeinträchtigen" und konzentrieren uns auf die Auswirkungen des Sprachdesigns.

Infix-Operatoren haben mehr Probleme als einfache Prioritätsregeln (obwohl der Link, auf den Sie verweisen, unverblümt ist, wird die Auswirkung dieser Entwurfsentscheidung nicht berücksichtigt). Eines ist Konfliktlösung: Was passiert, wenn Sie definieren a.operator+(b)und b.operator+(a)? Das Vorziehen einer über die andere führt dazu, dass die erwartete kommutative Eigenschaft dieses Operators verletzt wird. Das Auslösen eines Fehlers kann dazu führen, dass Module, die sonst funktionieren würden, einmal zusammengebrochen werden. Was passiert, wenn Sie abgeleitete Typen in den Mix werfen?

Tatsache ist, dass Operatoren nicht nur Funktionen sind. Funktionen sind entweder eigenständig oder gehören ihrer Klasse, wodurch eindeutig festgelegt wird, für welchen Parameter (falls vorhanden) die polymorphe Verteilung gilt.

Dabei werden die verschiedenen Verpackungs- und Auflösungsprobleme ignoriert, die von Bedienern verursacht werden. Der Grund, warum Sprachentwickler die Definition von Infix-Operatoren (im Großen und Ganzen) einschränken, liegt darin, dass sie der Sprache eine Menge Probleme bereiten und dabei einen fragwürdigen Nutzen bieten.

Und ehrlich gesagt, weil sie nicht trivial zu implementieren sind.

Telastyn
quelle
1
Ich glaube, das ist der Grund, warum Java sie nicht einschließt, aber Sprachen, die sie haben, haben klare Regeln für den Vorrang. Ich denke, es ist ähnlich wie bei der Matrixmultiplikation. Es ist nicht kommutativ, also müssen Sie sich bewusst sein, wenn Sie Matrizen verwenden. Ich denke, dasselbe gilt, wenn es um Klassen geht, aber ja, ich stimme zu, dass +es böse ist , nicht kommutativ zu sein . Aber ist das wirklich ein Argument gegen benutzerdefinierte Operatoren? Es scheint ein Argument gegen das Überladen von Operatoren im Allgemeinen zu sein.
Beatgammit
1
@tjameson - Ja, Sprachen haben klare Regeln für den Vorrang, für die Mathematik . Die Vorrangregeln für mathematische Operationen gelten wahrscheinlich nicht wirklich, wenn die Operatoren für Dinge wie überladen sind boost::spirit. Sobald Sie benutzerdefinierte Operatoren zulassen, die sich verschlechtern, gibt es keine gute Möglichkeit, den Vorrang für Mathematik genau zu definieren. Ich habe selbst ein wenig darüber im Kontext einer Sprache geschrieben, die speziell auf Probleme mit willkürlich definierten Operatoren abzielt.
Telastyn
22
Ich nenne Bullshit, Sir. Es gibt ein Leben außerhalb des OO-Ghettos und Funktionen gehören nicht unbedingt zu einem Objekt. Daher ist das Finden der richtigen Funktion genau das Gleiche wie das Finden des richtigen Operators.
Matthieu M.
1
@matthieuM. - Bestimmt. Besitz- und Versandregeln sind in den Sprachen, die keine Mitgliedsfunktionen unterstützen, einheitlicher. Zu einem bestimmten Zeitpunkt stellt sich jedoch die ursprüngliche Frage: Warum sind Nicht-OO-Sprachen nicht häufiger? Das ist ein ganz anderes Ballspiel.
Telastyn
11
Haben Sie sich angesehen, wie Haskell benutzerdefinierte Operatoren ausführt? Sie funktionieren genauso wie normale Funktionen, haben jedoch auch einen entsprechenden Vorrang. (Tatsächlich können sich normale Funktionen auch dort nicht wirklich unterscheiden.) Grundsätzlich sind Operatoren standardmäßig infixiert und Namen sind Präfixe, aber das ist der einzige Unterschied.
Tikhon Jelvis
19

Ich glaube , Sie wären überrascht , wie oft Betreiber Überlastungen werden umgesetzt in irgendeiner Form. In vielen Gemeinden werden sie jedoch nicht häufig verwendet.

Warum mit ~ zu einem Array verketten? Warum nicht << wie Ruby verwenden ? Weil die Programmierer, mit denen Sie arbeiten, wahrscheinlich keine Ruby-Programmierer sind. Oder D Programmierer. Was machen sie also, wenn sie auf Ihren Code stoßen? Sie müssen nachsehen, was das Symbol bedeutet.

Ich habe mit einem sehr guten C # -Entwickler zusammengearbeitet, der auch eine Vorliebe für funktionale Sprachen hatte. Aus heiterem Himmel begann er, Monaden über Erweiterungsmethoden und unter Verwendung der Standard-Monadenterminologie in C # einzuführen . Niemand konnte bestreiten, dass ein Teil seines Codes schärfer und noch besser lesbar war, wenn man wusste, was er bedeutete, aber es bedeutete, dass jeder die Monadenterminologie lernen musste, bevor der Code Sinn ergab .

Fair genug, denkst du? Es war nur ein kleines Team. Persönlich stimme ich nicht zu. Jeder neue Entwickler war dazu bestimmt, durch diese Terminologie verwirrt zu werden. Haben wir nicht genug Probleme beim Erlernen einer neuen Domain?

Auf der anderen Seite verwende ich den ??Operator gerne in C #, da ich erwarte, dass andere C # -Entwickler wissen, was es ist, aber ich würde es nicht in eine Sprache überladen, die es standardmäßig nicht unterstützt.

pdr
quelle
Ich verstehe Ihr Double.NaN-Beispiel nicht. Dieses Verhalten ist Teil der Gleitkommaspezifikation und wird in jeder von mir verwendeten Sprache unterstützt. Wollen Sie damit sagen, dass benutzerdefinierte Operatoren nicht unterstützt werden, da dies Entwickler verwirren würde, die sich mit der Funktion nicht auskennen? Das klingt nach demselben Argument gegen die Verwendung des ternären Operators oder Ihres ??Beispiels.
Beatgammit
@tjameson: Sie haben recht, das war ein bisschen tangential zu dem Punkt, den ich anstrebte, und nicht besonders gut geschrieben. Ich dachte an die ?? Beispiel, wie ich das schrieb und dieses Beispiel bevorzugen. Entfernen des double.NaN-Absatzes
pdr
6
Ich denke, Ihr Punkt "Jeder neue Entwickler war dazu bestimmt, durch diese Terminologie verwirrt zu werden" ist ein bisschen trübselig. Die Sorge um die Lernkurve neuer Entwickler für Ihre Codebasis ist für mich die gleiche wie die Sorge um die Lernkurve neuer Entwickler für eine neue Version von .NET. Ich denke wichtiger ist, wenn die Entwickler es lernen (neues .NET oder Ihre Codebasis), macht es Sinn und zeigt es Wert? In diesem Fall lohnt sich die Lernkurve, da jeder Entwickler sie nur einmal lernen muss, obwohl der Code unzählige Male gehandhabt werden muss. Wenn der Code also verbessert wird, sind die Kosten gering.
Jimmy Hoffa
7
@ JimmyHoffa Es ist eine wiederkehrende Kosten. Wird für jeden neuen Entwickler im Voraus bezahlt und wirkt sich auch auf vorhandene Entwickler aus, weil sie ihn unterstützen müssen. Es gibt auch Dokumentationskosten und ein gewisses Risiko - es fühlt sich heute sicher genug an, aber 30 Jahre später sind alle weitermachen, die Sprache und die Anwendung sind jetzt "Legacy", die Dokumentation ist ein riesiger dampfender Haufen und ein armer Trottel wird sich wegen der Brillanz von Programmierern, die zu faul waren, ".concat ()" einzugeben, die Haare reißen. Reicht der erwartete Wert aus, um die Kosten auszugleichen?
Sylverdrag
3
Auch das Argument "Jeder neue Entwickler war dazu bestimmt, durch diese Terminologie verwirrt zu werden. Haben wir nicht genug Probleme, eine neue Domäne zu erlernen?" hätte in den 80er Jahren auf OOP angewendet werden können, davor auf strukturierte Programmierung oder vor 10 Jahren auf IoC. Es ist an der Zeit, dieses trügerische Argument nicht weiter zu verwenden.
Mauricio Scheffer
11

Ich kann mir ein paar Gründe vorstellen:

  • Die Implementierung ist nicht trivial - das Zulassen von beliebigen benutzerdefinierten Operatoren kann Ihren Compiler wesentlich komplexer machen, insbesondere wenn Sie benutzerdefinierte Prioritäts-, Fixitäts- und Aritätsregeln zulassen. Wenn Einfachheit eine Tugend ist, führt eine Überlastung des Operators nicht zu einem guten Sprachdesign.
  • Sie werden missbraucht - hauptsächlich von Programmierern, die es für "cool" halten, Operatoren neu zu definieren und sie für alle Arten von benutzerdefinierten Klassen neu zu definieren. Es dauert nicht lange, bis Ihr Code mit einer Vielzahl von benutzerdefinierten Symbolen übersät ist, die niemand sonst lesen oder verstehen kann, da die Bediener die üblichen Regeln nicht befolgen. Ich kaufe das Argument "DSL" nicht, es sei denn, Ihr DSL ist eine Teilmenge der Mathematik :-)
  • Sie beeinträchtigen die Lesbarkeit und Wartbarkeit - wenn Bediener regelmäßig übersteuert werden, kann es schwierig werden, diese Funktion zu erkennen, und Programmierer sind gezwungen, sich ständig zu fragen, was ein Bediener tut. Es ist viel besser, aussagekräftige Funktionsnamen zu vergeben. Ein paar zusätzliche Zeichen einzugeben ist billig, langfristige Wartungsprobleme sind teuer.
  • Sie können implizite Leistungserwartungen brechen . Normalerweise würde ich beispielsweise erwarten, dass ein Element in einem Array nachgeschlagen wird O(1). Bei einer Überladung des Operators kann es sich someobject[i]jedoch leicht um eine O(n)Operation handeln, die von der Implementierung des Indizierungsoperators abhängt.

In der Realität gibt es nur sehr wenige Fälle, in denen das Überladen von Operatoren gerechtfertigt ist, verglichen mit der Verwendung regulärer Funktionen. Ein legitimes Beispiel könnte das Entwerfen einer Klasse für komplexe Zahlen sein, die von Mathematikern verwendet wird, die verstehen, wie mathematische Operatoren für komplexe Zahlen definiert werden. Aber das ist wirklich kein alltäglicher Fall.

Einige interessante Fälle zu berücksichtigen:

  • Lisps : Unterscheiden Sie im Allgemeinen überhaupt nicht zwischen Operatoren und Funktionen - dies +ist nur eine reguläre Funktion. Sie können Funktionen definieren, wie Sie möchten (normalerweise können Sie sie in separaten Namespaces definieren, um Konflikte mit den eingebauten zu vermeiden +), einschließlich Operatoren. Aber es gibt eine kulturelle Tendenz, sinnvolle Funktionsnamen zu verwenden, so dass dies nicht viel missbraucht wird. Darüber hinaus wird in der Präfixnotation von Lisp in der Regel ausschließlich verwendet, sodass der "syntaktische Zucker", den Operatorüberladungen bereitstellen, weniger Wert hat.
  • Java - Verhindert das Überladen von Operatoren. Dies ist gelegentlich ärgerlich (für Dinge wie den komplexen Zahlenfall), aber im Durchschnitt ist es wahrscheinlich die richtige Entwurfsentscheidung für Java, die als einfache, universelle OOP-Sprache gedacht ist. Aufgrund dieser Einfachheit ist Java-Code für Entwickler mit geringen oder mittleren Kenntnissen recht einfach zu warten.
  • C ++ verfügt über eine sehr ausgefeilte Operatorüberladung. Manchmal wird dies missbraucht ( cout << "Hello World!"irgendjemand?), Aber der Ansatz macht Sinn, wenn man bedenkt, dass C ++ eine komplexe Sprache ist, mit der man auf hohem Niveau programmieren kann, während man sich dennoch der Performance des Metals annähert, um beispielsweise eine komplexe Zahlenklasse zu schreiben, die sich verhält genau so, wie Sie es möchten, ohne die Leistung zu beeinträchtigen. Es liegt in Ihrer eigenen Verantwortung, wenn Sie sich in den Fuß schießen.
mikera
quelle
8

Warum ist diese Funktion nicht häufiger anzutreffen, da sie ziemlich einfach zu implementieren ist?

Es ist nicht trivial zu implementieren (es sei denn, es ist trivial implementiert). Es bringt Ihnen auch nicht viel, auch wenn es ideal implementiert ist: Die Lesbarkeitsgewinne durch Knappheit werden durch die Lesbarkeitsverluste durch Unvertrautheit und Undurchsichtigkeit ausgeglichen. Kurz gesagt, ist es ungewöhnlich, weil es normalerweise nicht die Zeit der Entwickler oder Benutzer wert ist.

Das heißt, ich kann mir drei Sprachen vorstellen, die das tun, und sie tun es auf unterschiedliche Weise:

  • Racket, ein Schema, wenn es nicht alles ist S-expression-y erlaubt und erwartet, dass Sie einen Parser für jede Syntax schreiben, die Sie erweitern möchten (und nützliche Hooks bereitstellen, um dieses Tractable zu erstellen).
  • Mit Haskell, einer reinen Funktionsprogrammiersprache, können Sie jeden Operator definieren, der ausschließlich aus Interpunktion besteht. Außerdem können Sie einen Fixitätsgrad (10 verfügbar) und eine Assoziativität angeben. Ternäre usw. Operatoren können aus binären Operatoren und Funktionen höherer Ordnung erstellt werden.
  • Agda, eine abhängig typisierte Programmiersprache, ist äußerst flexibel bei Operatoren (siehe unten ), sodass sowohl Wenn-Dann- als auch Wenn-Dann-Anderes als Operatoren in demselben Programm definiert werden können, aber Lexer, Parser und Evaluator sind stark miteinander verknüpft als Ergebnis.
Alex R
quelle
4
Sie haben gesagt, dass es nicht trivial ist, und sofort drei Sprachen aufgelistet, die einige unerhört triviale Implementierungen aufweisen. Also ist es doch ziemlich trivial, oder?
SK-logic
7

Einer der Hauptgründe, warum benutzerdefinierte Operatoren davon abgehalten werden, ist, dass jeder Operator irgendetwas bedeuten / können kann.

Zum Beispiel wird cstreamdie Überlastung der linken Schicht stark kritisiert.

Wenn eine Sprache Bedienerüberladungen zulässt, wird im Allgemeinen empfohlen, das Bedienerverhalten dem Basisverhalten ähnlich zu halten, um Verwirrung zu vermeiden.

Benutzerdefinierte Operatoren erschweren das Parsen erheblich, insbesondere wenn benutzerdefinierte Einstellungsregeln vorhanden sind.

Ratschenfreak
quelle
6
Ich verstehe nicht, wie die Teile über das Überladen von Operatoren auf das Definieren völlig neuer Operatoren zutreffen.
Ich habe gesehen, dass das Überladen von Operatoren (lineare Algebra-Bibliotheken) sehr gut und, wie Sie erwähnt haben, sehr schlecht funktioniert. Ich denke nicht, dass dies ein gutes Argument gegen benutzerdefinierte Operatoren ist. Das Parsen kann ein Problem sein, aber es ist ziemlich offensichtlich, wenn ein Operator erwartet wird. Nach dem Parsen muss der Codegenerator nach der Operatorbedeutung suchen.
Beatgammit
3
Und wie unterscheidet sich das von Methodenüberladung?
Jörg W Mittag
@ JörgWMittag bei Methodenüberladung wird (meistens) ein aussagekräftiger Name angehängt. mit einem symbol ist es schwieriger, genau zu erklären, was passieren wird
ratschenfreak
1
@ratchetfreak: Na ja. +addiere zwei Dinge, -subtrahiere sie, *multipliziere sie. Mein Gefühl ist, dass niemand den Programmierer zwingt, Funktionen / Methoden zu erstellen, die addtatsächlich irgendetwas hinzufügen und doNothingmöglicherweise Atomwaffen starten. Und a.plus(b.minus(c.times(d)).times(e)ist dann viel weniger lesbar a + (b - c * d) * e(zusätzlicher Bonus - wo im ersten Stich ein Fehler in der Transkription ist). Ich sehe nicht, wie zuerst aussagekräftiger ist ...
Maciej Piechotka
4

Wir verwenden keine benutzerdefinierten Operatoren aus dem gleichen Grund, aus dem wir keine benutzerdefinierten Wörter verwenden. Niemand würde ihre Funktion "sworp" nennen. Die einzige Möglichkeit, Ihre Gedanken an eine andere Person weiterzugeben, ist die Verwendung einer gemeinsamen Sprache. Das bedeutet, dass der Gesellschaft, für die Sie Ihren Code schreiben, sowohl Wörter als auch Zeichen (Operatoren) bekannt sein müssen.

Daher sind die Operatoren, die in Programmiersprachen verwendet werden, diejenigen, die uns in der Schule beigebracht wurden (Arithmetik) oder die in der Programmiergemeinschaft etabliert wurden, z. B. Boolesche Operatoren.

Vagif Verdi
quelle
1
Fügen Sie dem die Fähigkeit hinzu, die Bedeutung hinter einer Funktion zu entdecken. Im Falle von "sworp" können Sie es ganz einfach in ein Suchfeld stecken und dessen Dokumentation finden. Das Einbinden eines Betreibers in eine Suchmaschine ist ein ganz anderer Ballpark. Unsere derzeitigen Suchmaschinen sind einfach nur schlecht darin, Operatoren zu finden, und ich habe in einigen Fällen viel Zeit damit verschwendet, nach den Bedeutungen zu suchen.
Mark H
1
Wie viel "Schule" zählt ihr? Ich denke zum Beispiel, dass ∈ for elemeine großartige Idee ist und sicherlich ein Operator, den jeder verstehen sollte, aber andere scheinen anderer Meinung zu sein.
Tikhon Jelvis
1
Es ist nicht das Problem mit dem Symbol. Es ist das Problem mit der Tastatur. Ich verwende zum Beispiel den emacs haskell-Modus mit aktiviertem Unicode-Beautifier. Und es konvertiert automatisch ASCII-Operatoren in Unicode-Symbole. Aber ich wäre nicht in der Lage, es zu tippen, wenn es kein Ascii-Äquivalent gäbe.
Vagif Verdi
2
Natürliche Sprachen bestehen nur aus den "benutzerdefinierten Wörtern" und sonst nichts. Und einige Sprachen fördern tatsächlich die Bildung benutzerdefinierter Wörter.
SK-logic
4

Was Sprachen anbelangt, die eine solche Überladung unterstützen: Scala kann C ++ tatsächlich auf eine viel sauberere und bessere Art und Weise. Die meisten Zeichen können in Funktionsnamen verwendet werden, sodass Sie bei Bedarf Operatoren wie! + * = ++ definieren können. Es gibt eine integrierte Unterstützung für infix (für alle Funktionen, die ein Argument verwenden). Ich denke, Sie können auch die Assoziativität solcher Funktionen definieren. Sie können den Vorrang jedoch nicht definieren (nur mit hässlichen Tricks, siehe hier ).

kutschkem
quelle
4

Eine Sache, die noch nicht erwähnt wurde, ist der Fall von Smalltalk, bei dem alles (einschließlich Operatoren) eine Nachricht ist, die gesendet wird. „Operatoren“ wie +, |und so weiter sind tatsächlich einstellige Methoden.

Alle Methoden können außer Kraft gesetzt werden, so a + bbedeutet Integer - Addition , wenn aund bbeide ganze Zahlen sind, und bedeutet Vektoraddition , wenn sie beide OrderedCollections.

Es gibt keine Vorrangregeln, da dies nur Methodenaufrufe sind. Dies hat eine wichtige Bedeutung für die mathematische Standardnotation: 3 + 4 * 5bedeutet (3 + 4) * 5, nicht 3 + (4 * 5).

(Dies ist ein großer Stolperstein für Smalltalk-Neulinge. Durch das Brechen von Mathematikregeln wird ein Sonderfall beseitigt, sodass die gesamte Codebewertung von links nach rechts gleichmäßig verläuft und die Sprache so viel einfacher wird.)

Frank Shearar
quelle
3

Sie kämpfen hier gegen zwei Dinge:

  1. Warum existieren Operatoren überhaupt in Sprachen?
  2. Was ist die Tugend von Operatoren gegenüber Funktionen / Methoden?

In den meisten Sprachen sind Operatoren nicht wirklich als einfache Funktionen implementiert. Möglicherweise verfügen sie über ein Funktionsgerüst, aber der Compiler / die Laufzeit ist sich ausdrücklich ihrer semantischen Bedeutung bewusst und weiß, wie sie effizient in Maschinencode übersetzt werden können. Dies trifft sogar im Vergleich zu integrierten Funktionen zu (weshalb die meisten Implementierungen auch nicht den gesamten Funktionsaufruf-Overhead in ihre Implementierung einbeziehen). Die meisten Operatoren sind Abstraktionen auf höherer Ebene in primitiven Befehlen, die in CPUs zu finden sind (weshalb die meisten Operatoren teilweise arithmetisch, boolesch oder bitweise sind). Sie könnten sie als "spezielle" Funktionen modellieren (nennen Sie sie "primitive" oder "eingebaute" oder "native" oder was auch immer), aber um dies zu tun, ist im Allgemeinen eine sehr robuste Semantik zum Definieren solcher speziellen Funktionen erforderlich. Die Alternative besteht darin, eingebaute Operatoren zu haben, die semantisch wie benutzerdefinierte Operatoren aussehen, aber ansonsten spezielle Pfade im Compiler aufrufen. Das läuft der Antwort auf die zweite Frage zuwider ...

Abgesehen von dem oben erwähnten Problem der maschinellen Übersetzung unterscheiden sich Operatoren syntaktisch nicht wirklich von Funktionen. Sie zeichnen sich in der Regel dadurch aus, dass sie knapp und symbolisch sind, was auf ein signifikantes zusätzliches Merkmal hinweist, das nützlich sein muss: Sie müssen für Entwickler eine allgemein verstandene Bedeutung / Semantik haben. Kurze Symbole vermitteln nicht viel Bedeutung, es sei denn, es handelt sich um eine Kurzform für eine Reihe von Semantiken, die bereits verstanden wurden. Benutzerdefinierte Operatoren sind daher von Natur aus wenig hilfreich, da sie von Natur aus nicht so umfassend verstanden werden. Sie sind genauso sinnvoll wie Funktionsnamen mit einem oder zwei Buchstaben.

Die Überladungen der C ++ - Operatoren bieten einen fruchtbaren Grund, dies zu untersuchen. Die meisten Überladungen von Operatoren "Missbrauch" treten in Form von Überladungen auf, die einen Teil des allgemein verständlichen semantischen Vertrags brechen (ein klassisches Beispiel ist eine Überladung von Operatoren + mit a + b! = B + a oder wobei + eine der beiden Bedingungen ändert) Operanden).

Wenn Sie sich Smalltalk ansehen, das das Überladen von Operatoren und benutzerdefinierte Operatoren ermöglicht, können Sie sehen, wie eine Sprache dies tun könnte und wie nützlich es wäre. In Smalltalk sind Operatoren lediglich Methoden mit unterschiedlichen syntaktischen Eigenschaften (dh sie werden als Infix-Binärcode codiert). Die Sprache verwendet "primitive Methoden" für spezielle beschleunigte Operatoren und Methoden. Sie werden feststellen, dass nur wenige benutzerdefinierte Operatoren erstellt werden und dass sie in der Regel nicht so häufig verwendet werden, wie es der Autor wahrscheinlich beabsichtigt hat. Selbst das Äquivalent einer Operatorüberladung ist selten, da es meist ein Nettoverlust ist, eine neue Funktion als Operator anstelle einer Methode zu definieren, da letztere einen Ausdruck der Semantik der Funktion ermöglicht.

Christopher Smith
quelle
1

Ich habe immer festgestellt, dass Operatorüberladungen in C ++ eine bequeme Abkürzung für ein Einzelentwicklerteam sind, die aber auf lange Sicht alle möglichen Verwirrungen hervorruft, einfach weil die Methodenaufrufe auf eine Weise "versteckt" werden, die nicht einfach ist Werkzeuge wie Sauerstoff müssen auseinander genommen werden, und die Leute müssen die Redewendungen verstehen, um sie richtig nutzen zu können.

Manchmal ist es sogar viel schwieriger, einen Sinn zu finden, als man erwarten würde. In einem großen plattformübergreifenden C ++ - Projekt war es einmal eine gute Idee, die Art und Weise zu normalisieren, in der Pfade erstellt wurden, indem ein FilePathObjekt (ähnlich dem von Java File) erstellt wurde, mit dem ein anderer Operator / verkettet wurde Pfad-Teil darauf (also könnten Sie so etwas tun File::getHomeDir()/"foo"/"bar"und es würde auf allen von uns unterstützten Plattformen das Richtige tun). Jeder, der es sah, sagte im Wesentlichen: "Was zur Hölle? Streicherteilung? ... Oh, das ist süß, aber ich vertraue nicht darauf, dass es das Richtige tut."

Ebenso gibt es viele Fälle in der Grafikprogrammierung oder in anderen Bereichen, in denen Vektor- / Matrix-Mathematik häufig in Versuchung gerät, Dinge wie Matrix * Matrix, Vektor * Vektor (Punkt), Vektor% Vektor (Kreuz), Matrix * Vektor ( Matrixtransformation), Matrix ^ Vector (Spezialfall-Matrixtransformation, bei der die homogene Koordinate ignoriert wird - nützlich für Oberflächennormalen) usw. Dies spart zwar ein wenig Analysezeit für die Person, die die Vektor-Mathematik-Bibliothek geschrieben hat, endet jedoch nur up verwirrt das Thema mehr für andere. Das ist es einfach nicht wert.

flauschige
quelle
0

Überladungen von Operatoren sind aus dem gleichen Grund eine schlechte Idee wie Überladungen von Methoden: Dasselbe Symbol auf dem Bildschirm hat je nach Umgebung unterschiedliche Bedeutungen. Dies erschwert das gelegentliche Lesen.

Da die Lesbarkeit ein kritischer Aspekt der Wartbarkeit ist, sollten Sie Überladungen immer vermeiden (mit Ausnahme einiger sehr spezieller Fälle). Es ist weitaus besser, wenn jedes Symbol (ob Operator oder alphanumerischer Bezeichner) eine eigene Bedeutung hat.

Zur Veranschaulichung: Wenn Sie beim Lesen von unbekanntem Code auf eine neue alphanum-ID stoßen, die Sie nicht kennen, haben Sie zumindest den Vorteil, dass Sie wissen, dass Sie sie nicht kennen. Sie können es dann nachschlagen. Wenn Sie jedoch einen gemeinsamen Bezeichner oder Operator sehen, dessen Bedeutung Sie kennen, werden Sie mit weitaus geringerer Wahrscheinlichkeit feststellen, dass er tatsächlich überladen wurde, um eine völlig andere Bedeutung zu haben. Um zu wissen, welche Operatoren überladen wurden (in einer Codebasis, die das Überladen in großem Umfang nutzte), müssen Sie den gesamten Code kennen, auch wenn Sie nur einen kleinen Teil davon lesen möchten. Dies würde es schwierig machen, neue Entwickler auf den neuesten Stand zu bringen, und unmöglich machen, Leute für einen kleinen Job zu gewinnen. Dies ist zwar gut für die Arbeitssicherheit von Programmierern, aber wenn Sie für den Erfolg der Codebasis verantwortlich sind, sollten Sie diese Vorgehensweise unbedingt vermeiden.

Da Operatoren von geringer Größe sind, würde das Überladen von Operatoren einen dichteren Code ermöglichen, aber das Festlegen eines dichten Codes ist kein wirklicher Vorteil. Das Lesen einer Zeile mit der doppelten Logik dauert doppelt so lange. Dem Compiler ist das egal. Das einzige Problem ist die Lesbarkeit. Da das Kompaktmachen von Code die Lesbarkeit nicht verbessert, hat die Kompaktheit keinen wirklichen Vorteil. Nehmen Sie Platz und geben Sie eindeutigen Operationen eine eindeutige Kennung, damit Ihr Code auf lange Sicht erfolgreicher ist.

AgilePro
quelle
"Das gleiche Symbol auf dem Bildschirm hätte je nach Umgebung unterschiedliche Bedeutungen" - dies ist bereits für viele Bediener in den meisten Sprachen der Fall.
rkj
-1

Ich denke, dass es einige Aspekte einer Programmiersprache gibt, die berücksichtigt werden müssen, wenn es um technische Schwierigkeiten bei der Bearbeitung von Prioritäten und komplexem Parsing geht.

Operatoren sind in der Regel kurze logische Konstrukte, die in der Hauptsprache gut definiert und dokumentiert sind (vergleichen, zuweisen ..). Sie sind auch in der Regel schwer , ohne Dokumentation zu verstehen (vgl a^bmit xor(a,b)zum Beispiel). Es gibt nur eine begrenzte Anzahl von Operatoren, die bei normaler Programmierung sinnvoll sein könnten (>, <, =, + usw.).

Meine Idee ist, dass es besser ist, sich an eine Reihe klar definierter Operatoren in einer Sprache zu halten und dann eine Überladung dieser Operatoren durch die Operatoren zuzulassen (mit der sanften Empfehlung, dass die Operatoren dasselbe tun sollten, aber mit einem benutzerdefinierten Datentyp).

Ihre Anwendungsfälle von ~und |wären tatsächlich mit einer einfachen Überladung des Operators (C #, C ++ usw.) möglich. DSL ist ein gültiger Nutzungsbereich, aber wahrscheinlich einer der wenigen gültigen Bereiche (aus meiner Sicht). Ich denke jedoch, dass es bessere Werkzeuge gibt, um neue Sprachen zu erstellen. Das Ausführen einer echten DSL-Sprache in einer anderen Sprache ist mit einem dieser Compiler-Compiler-Tools nicht so schwierig. Gleiches gilt für das Argument "LUA erweitern". Eine Sprache wird höchstwahrscheinlich in erster Linie definiert, um Probleme auf eine bestimmte Art und Weise zu lösen, und ist keine Grundlage für Untersprachen (Ausnahmen sind vorhanden).

Petter Nordlander
quelle
-1

Ein weiterer Faktor dafür ist, dass es nicht immer einfach ist, eine Operation mit den verfügbaren Operatoren zu definieren. Ich meine, ja, für jede Art von Nummer kann der Operator '*' sinnvoll sein und wird normalerweise in der Sprache oder in vorhandenen Modulen implementiert. Bei den typischen komplexen Klassen, die Sie definieren müssen (z. B. ShipingAddress, WindowManager, ObjectDimensions, PlayerCharacter usw.), ist dieses Verhalten jedoch nicht klar. Was bedeutet das Hinzufügen oder Subtrahieren einer Zahl zu einer Adresse? Zwei Adressen multiplizieren?

Sicher können Sie definieren, dass das Hinzufügen einer Zeichenfolge zu einer ShippingAddress-Klasse einen benutzerdefinierten Vorgang wie "Zeile 1 in Adresse ersetzen" (anstelle der Funktion "setLine1") und das Hinzufügen einer Nummer "Postleitzahl ersetzen" (anstelle von "setZipCode") bedeutet. , aber dann ist der Code nicht sehr lesbar und verwirrend. Wir glauben normalerweise, dass Operatoren in grundlegenden Typen / Klassen verwendet werden, da ihr Verhalten intuitiv, klar und konsistent ist (zumindest, wenn Sie mit der Sprache vertraut sind). Denken Sie an Typen wie Integer, String, ComplexNumbers usw.

Auch wenn das Definieren von Operatoren in bestimmten Fällen sehr nützlich sein kann, ist ihre Implementierung in der Praxis sehr begrenzt, da 99% der Fälle, in denen dies ein klarer Gewinn sein wird, bereits im Basissprachenpaket implementiert sind.

Khelben
quelle