Julia: Benutzerdefinierte Infix-Operatoren

Erstellt am 17. Juni 2016  ·  65Kommentare  ·  Quelle: JuliaLang/julia

Es gibt eine Diskussion unter https://groups.google.com/forum/#!topic/julia -dev/FmvQ3Fj0hHs über das Erstellen einer Syntax für benutzerdefinierte Infix-Operatoren.

...

Bearbeitet, um einen Hinweis hinzuzufügen: @johnmyleswhite hat darauf hingewiesen, dass der Kommentarthread unten eine Einladung zum Bikeshedding ist. Bitte sehen Sie von neuen Kommentaren ab, es sei denn, Sie haben etwas wirklich Neues hinzuzufügen. Unten finden Sie mehrere Vorschläge, die durch "Hurra"-Emoticons (explodierender Kegel) gekennzeichnet sind. Sie können diese Symbole verwenden, um Diskussionen zu überspringen und nur die Vorschläge zu lesen, oder um die verschiedenen Vorschläge zu finden, damit Sie mit "Daumen hoch" oder "Daumen runter" stimmen können.

Up/Downvotes zu diesem Bug als Ganzes drehen sich darum, ob Sie denken, dass Julia irgendein benutzerdefiniertes Infix-Idiom haben sollte. Up/Downvotes für die spezifische Idee unten sollten in den ersten Kommentar von @Glen-O aufgenommen werden. (Der Fehler hatte 3 Downvotes und 1 Upvote, bevor das geklärt wurde.)

...

Ursprünglicher Vorschlag (nur historisches Interesse):

Der Vorschlag, der sich anscheinend durchgesetzt hat, lautet:

    a |>op<| b #evaluates (in the short term) and parses (in the long term) to `op(a,b)`

Um diese Arbeit zu haben, sind nur geringfügige Änderungen erforderlich:

  • Stellen Sie die Priorität von <| über die von |> , anstatt dieselbe zu sein.
  • Bilden Sie eine Gruppe von <| links nach rechts.
  • Machen Sie die Funktion <|(a,b...)=(i...)->a(i...,b...) . (wie im Diskussionsthread erwähnt, hätte dies eigenständige Verwendungen sowie seine Verwendung in der obigen Redewendung)

Optional:

  • Erstellen Sie neue Funktionen >|(a...,b)=(i...)->b(a...,i...) und |<(a,b...)=a(b...) mit entsprechenden Vorrang und Gruppierung.

    • Pipe first bedeutet Auswertung und Pipe last behält es als Funktion bei, während > und < angeben, welches die Funktion ist.

  • Erstellen Sie neue Funktionen >>|(a...,b)=(i...)->b(i...,a...) und <<|(a,b...)=(i...)->a(b...,i...) mit entsprechender Priorität und Gruppierung.
  • Synonyme » , und (/oder) pipe für |> erstellen; « , und (/oder) rcurry für <| ; und (/oder) lcurry für <<| ; wobei die Einzelzeichen-Synonyme als Infix-Operatoren fungieren.
  • Erstellen Sie ein @infix -Makro in base, das die erste Parser-Korrektur unten durchführt.

Langfristig:

  • Bringen Sie dem Parser bei, a |>op<| b in op(a,b) zu ändern, damit beim Ausführen des Codes kein zusätzlicher Aufwand entsteht und Operatoren tatsächlich in Infix-Position definiert werden können. (Dies ähnelt der Art und Weise, wie der Parser derzeit das binäre a:b und das ternäre a:b:c unterschiedlich behandelt. Für maximale Anpassbarkeit sollte er dies für übereinstimmende Synonyme tun, aber nicht für nicht übereinstimmende Synonyme, sodass z a |> b « c würde immer noch als zwei binäre Operatoren behandelt.)
  • Bringen Sie dem Parser bei, Kommas und/oder Leerzeichen zu verstehen, damit die Ellipsen in den obigen Definitionen ohne zusätzliche Klammern wie erwartet funktionieren.

(bezieht sich auf https://github.com/JuliaLang/julia/issues/6946)

parser speculative

Hilfreichster Kommentar

Stefan ist nicht älter als ich.

Alle 65 Kommentare

In Anlehnung an den Julia-Dev-Thread denke ich, dass es nützlich wäre, Stefans Hauptkommentar zu diesem Vorschlag zu zitieren:

Nur um hier die Erwartungen zu wecken, ich glaube nicht, dass es vor Julia 1.0 viel an "syntaktischer Innovation" geben wird. (Die einzige Ausnahme, die mir einfällt, ist die neue f.(v) vektorisierte Aufrufsyntax.) Es mag zwar nett sein, beliebige Funktionen dazu zu bringen, sich wie Infix-Operatoren zu verhalten, aber es ist einfach kein dringendes Problem in der Sprache.

Als jemand, der an einem guten Teil der Geschichte der Julia-Entwicklung beteiligt war, denke ich, dass es besser wäre, die Energie auf semantische Änderungen zu konzentrieren als auf syntaktische. Es gibt noch viele äußerst wichtige semantische Probleme, die gelöst werden müssen, bevor Julia 1,0 erreicht.

Beachten Sie insbesondere, dass die Implementierung dieser Funktion nicht nur ein einmaliger Unterschied ist, über den nur der Autor nachdenken muss: Jeder muss darüber nachdenken, wie seine Arbeit in Zukunft mit dieser Funktion interagiert, sodass die Änderung langfristig zunimmt Arbeitsbelastung jeder Person, die am Parser arbeitet.

Ich denke, dass die Kommentare von Johnmyleswhite in Bezug auf die vorgeschlagenen "langfristigen" Parser-Änderungen sehr passend sind. Aber die Gruppen "geringfügige Änderungen" und "optional" sind, soweit ich das beurteilen kann, ziemlich in sich abgeschlossen und haben nur geringe Auswirkungen.

Das heißt: Die Parser-Änderungen, die erforderlich sind, um die minimale Version dieses Vorschlags zu ermöglichen, betreffen nur Vorrang und Gruppierung für normale binäre Operatoren, die Art von Änderungen, die in anderen Fällen mehr oder weniger Routine sind. Ein Parser-Entwickler, der an etwas Unabhängigem arbeitet, müsste dies ebenso wenig im Auge behalten wie die Bedeutung all der zahlreichen bereits existierenden Operatoren.

Ich persönlich finde diese Syntax ziemlich hässlich und schwer zu tippen. Aber ich stimme zu, dass es gut wäre, eine allgemeinere Infix-Syntax zu haben.

Ich denke, der richtige Weg, darüber nachzudenken, ist ein reines Syntaxproblem: Was Sie wollen, ist die Verwendung op mit Infix-Syntax, also die Definition anderer Funktionen und Operatoren, um das zu bekommen, ist ein Umweg. Mit anderen Worten, es sollte alles im Parser erledigt werden.

Ich würde tatsächlich erwägen, dafür | zurückzufordern und a |op| b zu verwenden. Die allgemeine Infix-Syntax ist wohl wichtiger als das bitweise Oder. (Wir haben bereits darüber gesprochen, bitweise Operatoren zurückzugewinnen; sie scheinen ohnehin ein bisschen Syntaxverschwendung zu sein.)

a f b ist außerhalb von Array-Verkettungen und Makroaufrufsyntaxen verfügbar.

a f b könnte funktionieren, aber es scheint ziemlich zerbrechlich. Stellen Sie sich vor, Sie versuchen jemandem zu erklären, warum a^2 f b^2 f c^2 legal ist, a f b c und a+2 f b+2 f c+2 jedoch nicht. (Ich weiß, letzteres geht davon aus, dass die Präzessionen Vorrang haben, aber egal, was der Vorrang ist, diese allgemeine Art von Dingen ist ein Problem).

Zu a |op| b : Anfangs favorisierte ich einen ähnlichen Vorschlag, a %op% b , wie Sie im Google Groups-Thread sehen können. Aber das Schöne an den vorgeschlagenen |> und <| ist, dass sie jeweils einzeln als binäre Operatoren nützlich sind und sich auf natürliche Weise kombinieren lassen, um wie gewünscht zu funktionieren (das heißt, mit der richtigen Priorität und Gruppierung. ) Dies bedeutet, dass Sie dies kurzfristig mit vorhandenen Parser-Mechanismen implementieren können und somit vermeiden, den Parser-Entwicklern in Zukunft Kopfschmerzen zu bereiten, wie ich oben in meiner Antwort auf johnmyleswhite sagte.

Obwohl ich a |op| b mag und sicherlich nichts dagegen hätte, denke ich, dass wir nach einer Möglichkeit suchen sollten, zwei verschiedene Operatoren zu haben, um die erforderlichen Parser-Änderungen zu vereinfachen. Wenn wir maximale Typisierbarkeit anstreben und nicht dagegen sind, dass | "Pipe" statt "bitwise or" bedeutet, was ist dann mit a |op\\ b oder a |op& b ?

"Kopfschmerzen für Parser-Entwickler" ist die geringstmögliche Sorge.

"Kopfschmerzen für Parser-Entwickler" ist die geringstmögliche Sorge.

Als Parser-Entwickler stimme ich dem uneingeschränkt zu.

|> und <| sind beide vollkommen gute Infix-Operatoren, aber es bringt keinen Vorteil, die allgemeine Operatorsyntax mit zwei anderen Operatoren zu implementieren. Und es muss noch viel mehr darüber gesagt werden, wie ausführlich und unattraktiv diese Syntax ist.

Es gibt keinen Vorteil, die allgemeine Operatorsyntax mit zwei anderen Operatoren zu implementieren.

Um es klar zu sagen, die langfristige Vision hier ist, dass es binäre f <| y , binäre x |> f und ternäre x |> f <| z geben würde, wobei die erste nur eine Funktion ist, die zweite jedoch zwei sind als Transformationen im Parser implementiert.

Die Idee, dass dies mit zwei gewöhnlichen Funktionen |> und <| implementiert werden könnte, ist nur eine vorübergehende Brücke zu dieser Vision.

Und es muss noch viel mehr darüber gesagt werden, wie ausführlich und unattraktiv diese Syntax ist.

Das ist ein fairer Punkt. Wie wäre es, wenn Sie |> und <| durch | und & ersetzen? Sie machen sowohl als Paar als auch einzeln Sinn, obwohl sie für einen kleinen Eishockeyspieler etwas störend sein könnten.

Sowohl | als auch & dafür zu stehlen, wäre keine gute Zuordnung von ASCII, und ich vermute, viele würden es vorziehen, wenn die Trennzeichen symmetrisch wären.

Wenn Leute aus anderen Gründen einen x |> f <| y ternären Operator wollen, ist das in Ordnung, aber ich denke, es sollte separat betrachtet werden. Ich bin mir nicht sicher, ob der Parser |> in ein umgedrehtes <| umwandeln sollte. Andere ähnliche Operatoren wie < funktionieren nicht auf diese Weise. Aber das ist auch ein separates Thema.

Beides stehlen | und & dafür wäre keine gute Zuordnung von ASCII, und ich vermute, viele würden es vorziehen, dass die Trennzeichen symmetrisch sind.

OK.

Ich verstehe, dass > und < schwer zu tippen sind. In Bezug auf Symmetrie und Tippbarkeit auf einer Standardtastatur ist das Einfachste wohl so etwas wie &% und %& , aber das ist wirklich hässlich, R parallel oder nein. /| und |/ könnten auch eine Überlegung wert sein.

...

Ich bin mir nicht sicher, ob der Parser |> in ein umgedrehtes <| umwandeln sollte

Ich glaube du hast es falsch verstanden. a |> b sollte zu b(a) parsen. (Die Version ohne spezielles Parsing wäre ((x,y)->y(x))(a,b) , was dasselbe auswertet, aber mit mehr Overhead.)

a |> b sollte zu b(a) parsen

Ah, ok, verstanden.

Ich denke, wir könnten jahrelang darüber diskutieren, welche Charaktere wir verwenden sollen. Ich würde @StefanKarpinski (als bisher ranghöchster Person in diesem Gespräch) vertrauen, eine Entscheidung zu treffen, und damit wäre ich einverstanden. Auch wenn ich dagegen argumentiert habe (wie a f b .)

Hier sind einige Optionen, um zu sehen, was Ihnen gefällt:
a |>op<| b (aktuelles |> unverändert lassen)
a |{ op }| b (in der Nähe und gleicher Umschaltstatus auf vielen gängigen Tastaturen, nicht zu hässlich. Etwas seltsam als eigenständige Tastaturen.)
a \| op |\ b oder a /| op |/ b oder Kombinationen davon
a $% op %$ b (relativ gut schreibbar, R-inspiriert. Aber irgendwie hässlich.)
a |% op %| b
a |- op -| b
a |: op :| b
a | op \\ b
a | op ||| b
a op b

Stefan ist nicht älter als ich.

Sieht so aus, als hätten Sie sich gerade selbst für die Befugnisse des BDFL zu diesem Thema nominiert! ;)

a @op@ b ?

Ich schätze, meine Stimme ist, alle 4 von \| , |\ , /| und |/ zu verwenden. Unten für die Bewertung, oben für Curry; Balken gegenüber der Funktion. So:
a \| f (oder f |/ a ) -> f(a)
a /| f (oder f |\\ a ) -> (b...)->f(a,b...)
f |\ b (oder b //| f ) -> (a...)->f(a...,b)
und somit:
a \| f |\ b (oder a /| f |/ b ) -> f(a,b)
a \| f |\ b |\ c (oder a /| b /| f |/ c ) -> f(a,b,c)

Jeder der 4 Hauptoperatoren, außer vielleicht |/ , ist für sich genommen nützlich. Die Redundanz wäre sicherlich un-pythonisch, aber ich denke, dass die logische Ordentlichkeit Julian ist. Und aus praktischen Gründen können Sie jede Version des Infix-Idioms verwenden, die Sie einfacher zu tippen finden; Sie sind beide gleichermaßen lesbar, da Sie, sobald Sie eines gelernt haben, natürlich beide verstehen.

Offensichtlich wäre es genauso sinnvoll, wenn Sie alle Schrägstriche vertauschen würden, sodass die Aufwärtspfeile für die Bewertung und die Abwärtspfeile für das Curry wären.

Ich warte immer noch auf Nachricht von On High (und ich entschuldige mich für meine Ungeschicklichkeit als Neuling beim Erraten, was das bedeutete). Aber wenn jemand, der größer als dieser Fahrradschuppen ist, für diese oder eine andere Version mit mindestens zwei neuen Symbolen eine Entscheidung trifft, würde ich gerne einen kurzfristigen Patch (mit Funktionen) und/oder einen richtigen (mit Transformationen) schreiben.

Wir versuchen, einen BDFL so weit wie möglich zu vermeiden :)

Ich dachte nur, ich notiere ein paar schnelle Dinge.

Erstens besteht der andere Vorteil (die "eigenständigen Verwendungen") der vorgeschlagenen Notation darin, dass <| in anderen Kontexten verwendet werden kann, um die Lesbarkeit zu verbessern. Wenn Sie beispielsweise ein Array von Zeichenfolgen haben, A , und alle links auf 10 auffüllen möchten, müssen Sie jetzt map(i->lpad(i,10),A) schreiben. Das ist relativ schwer zu lesen. Mit dieser Notation wird es zu map(lpad<|10,A) , was meiner Meinung nach deutlich sauberer ist.

Zweitens ist die Idee dahinter, die Notation konsistent zu halten. Es gibt bereits einen |> -Operator, der existiert, um das "Fix" eines Funktionsaufrufs von Präfix zu Postfix zu ändern. Dies erweitert nur die Notation.

Drittens hat die Möglichkeit, direktes Infix als a f b zu verwenden, ein größeres Problem. a + b und a * b müssten am Ende dieselbe Priorität haben, da + und * Funktionsnamen sind und es für das System nicht möglich wäre haben einen variablen Vorrang. Das, oder es müsste bestehende Infix-Operatoren anders behandeln, was zu Verwirrung führen könnte.

Wenn Sie beispielsweise ein Array von Zeichenfolgen haben, A , und alle links auf 10 auffüllen möchten, müssen Sie jetzt map(i->lpad(i,10),A) schreiben. Das ist relativ schwer zu lesen. Mit dieser Notation wird es zu map(lpad<|10,A) , was meiner Meinung nach deutlich sauberer ist.

Dem stimme ich ausdrücklich nicht zu. Die vorgeschlagene Syntax ist – verzeihen Sie mir – ASCII-Salat, der an einige der schlimmsten Vergehen von Perl und APL grenzt, ohne Präzedenzfall in anderen Sprachen, um dem gelegentlichen Leser einen Hinweis darauf zu geben, was passiert. Die aktuelle Syntax, obwohl sie ein paar Zeichen länger ist (fünf?), ist für jeden ziemlich klar, der weiß, dass i->expr eine Lambda-Syntax ist – was sie in einer großen und wachsenden Anzahl von Sprachen ist.

a + b und a * b müssten am Ende denselben Vorrang haben, da + und * Funktionsnamen sind und es für das System unmöglich wäre, einen variablen Vorrang zu haben. Das, oder es müsste bestehende Infix-Operatoren anders behandeln, was zu Verwirrung führen könnte.

Ich glaube nicht, dass das ein echtes Problem ist; wir können einfach sagen, was die Priorität von a f b Infix ist, und alle bestehenden Prioritätsebenen ebenfalls beibehalten. Dies funktioniert, weil der Vorrang durch den Namen der Funktion bestimmt wird; Jede Funktion mit dem Namen "+" hat den Vorrang "+".

Ja, wir tun dies bereits für die 1+2 in 1+2 -Syntax, und es war kein Problem.

Ich glaube nicht, dass das ein echtes Problem ist; Wir können einfach sagen, was der Vorrang von afb infix ist, und auch alle vorhandenen Vorrangstufen beibehalten. Dies funktioniert, weil der Vorrang durch den Namen der Funktion bestimmt wird; Jede Funktion mit dem Namen "+" hat den Vorrang "+".

Ich meinte nicht, dass es schwierig ist, den Parser zu schreiben, damit er funktioniert. Ich meinte, es führt zu Konsistenzproblemen, daher sagte ich "oder es müsste bestehende Infix-Operatoren anders behandeln, was zu Verwirrung führen könnte". Bedenken Sie unter anderem, dass ¦ und konzeptionell nicht allzu unterschiedlich aussehen, aber einer ein vordefinierter Infix-Operator ist, während der andere es nicht ist.

Dem stimme ich ausdrücklich nicht zu. Die vorgeschlagene Syntax ist – verzeihen Sie mir – ASCII-Salat, der an einige der schlimmsten Vergehen von Perl und APL grenzt, ohne Präzedenzfall in anderen Sprachen, um dem gelegentlichen Leser einen Hinweis darauf zu geben, was passiert. Die aktuelle Syntax, obwohl sie ein paar Zeichen länger ist (fünf?), ist für jeden ziemlich klar, der weiß, dass i->expr eine Lambda-Syntax ist – was sie in einer großen und wachsenden Anzahl von Sprachen ist.

Vielleicht sollte ich klarer sein, was ich sage. Ich sage, dass die Beschreibung der Operation als "lpad by 10" viel klarer ist, als es i->lpad(i,10) macht. Und meiner Ansicht nach kommt lpad<|10 dem am nächsten, in einer nicht kontextspezifischen Form.

Vielleicht würde es helfen, wenn ich beschreibe, woher ich komme. Ich bin in erster Linie Mathematiker und mathematischer Physiker, und die "Lambda-Syntax" ist zwar aus Programmiersicht sinnvoll, aber für diejenigen, die weniger Programmiererfahrung haben, nicht die klarste. Julia ist, wie ich es verstehe, in erster Linie darauf ausgerichtet, eine wissenschaftliche Computersprache zu sein, daher die starke Ähnlichkeit mit MATLAB.

Ich muss fragen - wie ist lpad<|10 mehr "ASCII-Salat" als, sagen wir, x|>sin|>exp ? Dennoch wurde die Notation |> hinzugefügt. Vergleichen Sie es beispielsweise mit Bash-Skripten, bei denen | verwendet wird, um das Argument auf der linken Seite an den Befehl auf der rechten Seite zu übergeben - wenn Sie wissen, dass es "Pipe" heißt, macht es _etwas_ mehr Sinn, aber wenn Sie es tun Wenn Sie keine Programmierkenntnisse haben, ergibt das keinen Sinn. In dieser Hinsicht macht |> eigentlich mehr Sinn, da es vage wie ein Pfeil aussieht. Und dann ist <| eine natürliche Erweiterung der Notation.

Vergleichen Sie mit einigen der anderen Vorschläge, wie %func% , die _tatsächlich_ einen Präzedenzfall in einer anderen Sprache haben, aber für Leute, die keine umfassenden Programmierkenntnisse in der Sprache haben, völlig undurchsichtig sind.

Wohlgemerkt, ich habe ein wenig auf eine der älteren Diskussionen zurückgeschaut, und ich sehe, dass es eine Notation gegeben hat, die in einer anderen Sprache verwendet wurde, die theoretisch ganz nett wäre. Haskell verwendet offenbar a |> b c d , um b(a,c,d) darzustellen. Wenn Leerzeichen nach einem Funktionsnamen es Ihnen erlauben würden, "Parameter" auf diese Weise anzugeben, würde es gut funktionieren - map(lpad 10,A) . Das einzige Problem tritt bei den unären Operatoren auf - map(+ 10,A) würde beispielsweise einen Fehler erzeugen, da es als "+10" anstelle von i->+(i,10) interpretiert würde.

Auf a f b : Die Vorrangprobleme sind vielleicht nicht so schlimm, wie Glen-O vorgeschlagen hat, aber wenn benutzerdefinierte Infix-Funktionen nicht den allerniedrigsten Vorrang haben, existieren sie. Sagen wir, um der Argumentation willen geben wir ihnen Prec-Zeiten. In diesem Fall,
a^2 f b^2 => f(a^2,b^2)
a+2 f b+2 => a+f(2,b)+2
a^2 f^2 b^2 => (f^2)(a^2,b^2)
a f+2 b => Syntaxfehler?

Dies ist alles eine natürliche Folge davon, wie Sie den Parser schreiben würden, also ist es in diesem Sinne kein besonderes Problem. Aber es ist nicht besonders intuitiv für den gelegentlichen Benutzer des Idioms.

Über die Nützlichkeit eines Curry-Idioms
Ich stimme Glen-O zu, dass (i)->lpad(i,10) einfach schlechter ist als lpad<|10 (oder, wenn wir so wollen, lpad |\ 10 oder was auch immer). Das i ist eine völlig fremde kognitive Belastung und potenzielle Fehlerquelle; Tatsächlich schwöre ich, dass ich, als ich das gerade eben geschrieben habe, anfangs versehentlich (i)->lpad(x,10) eingegeben habe. Eine Infix-Curry-Operation scheint mir also eine gute Idee zu sein.
Wenn dies jedoch die Absicht ist, können wir unabhängig von der Infix-Sprache, für die wir uns entscheiden, unsere eigene Curry-Operation erstellen. Wenn es a f b ist, dann wäre etwas wie lpad rcurry 10 in Ordnung. Der Punkt ist Lesbarkeit, nicht Tastenanschläge. Daher denke ich, dass dies nur ein schwaches Argument für <| ist.

Auf a |> b c d
Dieser Vorschlag gefällt mir sehr gut. Ich denke, wir könnten es so machen, dass |> Leerzeichen auf beiden Seiten akzeptiert, also a b |> f c d => f(a,b,c,d) .

(Anmerkung: Wenn sowohl mein Vorschlag von a b |> f c d als auch Glen-O's von map(lpad 10,A) , entsteht ein Eckfall: (a b) |> f c d => f((x)->a(x,b),c,d) . Aber ich Denke das ist erträglich.)

Dies hat immer noch ähnliche Probleme in Bezug auf die Operatorpriorität wie a f b . Aber irgendwie denke ich, dass sie erträglicher sind, wenn Sie zumindest in Bezug auf den Vorrang des Operators |> über sie sprechen können, anstatt den Vorrang des ternären Operators von mit zu haben

Versuchen Sie lpad.(["foo", "bar"], 10) auf 0.5. Das bestehende |> wird nicht gerade von allen geliebt.

@tkelman : Ich sehe das Problem, aber was willst du damit sagen? Denken Sie, wir sollten das vorhandene |> reparieren, bevor wir zusätzliche Verwendungsmöglichkeiten dafür hinzufügen? Wenn das so ist, wie?

Ich persönlich denke, wir sollten die bestehenden |> loswerden.

Versuchen Sie lpad.(["foo", "bar"], 10) auf 0.5. Das bestehende |> wird nicht gerade von allen geliebt.

Ich glaube, Sie haben den Punkt verfehlt. Ja, die Notation func.() ist nett und umgeht das Problem in einigen Situationen. Aber ich verwende die Funktion map als einfache Demonstration. Jede Funktion, die eine Funktion als Argument akzeptiert, würde von diesem Setup profitieren. Als Beispiel, nur um meinen Standpunkt zu demonstrieren, möchten Sie vielleicht einige Zahlen basierend auf ihrem kleinsten gemeinsamen Vielfachen mit einer Referenznummer sortieren. Was sieht ordentlicher und leichter zu lesen aus: sort(A,by=i->lcm(i,10)) oder sort(A,by=lcm 10) ?

Ich möchte noch einmal darauf hinweisen, dass jede Möglichkeit, Infix-Operatoren zu definieren, es ermöglicht, einen Operator zu erstellen, der das tut, was Glen-O <| will, sodass er im schlimmsten Fall etwas wie sort(A,by=lcm |> currywith 10) schreiben kann a ... f ... b => f(a,b) macht. Ich verstehe, dass die Frage, ob die bestehenden |> oder die vorgeschlagenen <| lohnenden Operatoren sind, in gewissem Zusammenhang zu diesem Punkt steht, aber lassen Sie uns versuchen, nicht zu sehr vom Weg abzukommen.

Ich persönlich denke, dass der a |> b c -Vorschlag bisher der beste ist. Es folgt einer bestehenden Konvention von Haskell; er ist logisch mit dem existierenden |> -Operator verwandt; es ist sowohl einigermaßen lesbar als auch einigermaßen einfach zu tippen. Die Tatsache, dass ich das Gefühl habe, dass es sich natürlich auf andere Verwendungszwecke erstreckt, ist zweitrangig. Wenn Sie nicht einverstanden sind, erwähnen Sie bitte zumindest Ihre Meinung zur Kernsprache, nicht nur zu den vorgeschlagenen sekundären Verwendungen.

Ich meinte, es führt zu Konsistenzproblemen, daher sagte ich "oder es müsste bestehende Infix-Operatoren anders behandeln, was zu Verwirrung führen könnte".

Ich stimme zu, dass es schwierig ist, die Priorität für a f b . Zum Beispiel profitiert in eindeutig von der Vergleichspriorität, aber es ist sehr wahrscheinlich, dass viele Funktionen, die als Infix verwendet werden, keine Vergleichspriorität wünschen. Allerdings sehe ich kein Konsistenzproblem. Unterschiedliche Operatoren haben unterschiedliche Priorität. Das Hinzufügen a f b zwingt unsere Hand nicht dazu, + und * denselben Vorrang einzuräumen.

Beachten Sie, dass |> neben dem Vergleich bereits Vorrang hat. Für jeden anderen Vorrang, ehrlich gesagt, denke ich, dass Klammern in Ordnung sind.

Wenn Sie mir nicht zustimmen und wir a |> f b verwenden würden, dann könnte es ähnliche Operatoren |+> , |*> und |^> geben, die funktionierte genauso wie |> , hatte aber Vorrang vor ihrem zentralen Operator. Ich denke, das ist übertrieben, aber es ist eine Möglichkeit.

Eine andere Möglichkeit, das Vorrangproblem zu lösen, besteht darin, eine Syntax für benutzerdefinierte Infix-Operatoren zu verwenden, die irgendeine Art von Klammern enthält, z. B. (a f b) .

Verwandte Diskussionen: https://github.com/JuliaLang/julia/issues/554 , https://github.com/JuliaLang/julia/issues/5571 , https://github.com/JuliaLang/julia/pull/14476 , https://github.com/JuliaLang/julia/issues/11608 und https://github.com/JuliaLang/julia/issues/15612.

Ich muss fragen - wie ist lpad<|10 mehr "ASCII-Salat" als, sagen wir, x|>sin|>exp? Allerdings wurde die Notation |> hinzugefügt.

Ich stelle mir vor, dass @tkelman argumentiert

wir sollten das vorhandene |> loswerden.

zum Teil, weil _sowohl_ lpad<|10 als auch x|>sin|>exp sich in ASCII-Salat-Gebiete wagen :).

Ich denke, (a f b) @toivoh mit obligatorischen Klammern ist bisher der beste Vorschlag.

Bezogen auf https://github.com/JuliaLang/julia/issues/11608 (und damit auch https://github.com/JuliaLang/julia/issues/4882 und https://github.com/JuliaLang/julia/pull /14653): Wenn (a f b) => f(a,b) , dann wäre es sinnvoll wenn (a <strong i="8">@m</strong> b) => (<strong i="10">@m</strong> a b) . Dies würde es ermöglichen, die vorhandene Spezialfall-Makrologik für y ~ a*x+b durch normale (und damit viel transparentere) (y @~ a*x+b) zu ersetzen.

Außerdem könnte das "parens required" die bevorzugte Redewendung für prägnante Infix-Definitionen sein. Anstatt (um ein dummes Beispiel zu verwenden) a + b = string(a) * string(b) zu sagen, würden Sie (durch Lint-Tools oder durch Compiler-Warnungen) ermutigt, (a + b) = string(a) * string(b) zu sagen. Mir ist klar, dass dies eigentlich keine direkte Folge der Wahl der Option "parens required" für Infix ist, aber es ist eine praktische Redewendung, die es uns ermöglichen würde, die Leute zu warnen, die Infix auf der LHS fälschlicherweise verwenden, aber die Leute, die es tun, zu entlassen auf Zweck.

Mein Gefühl ist derzeit, dass, wenn Sie ein Funktionsinfix (anstelle eines Präfixes) anwenden,
dann ist es ein Operator und sollte wie ein Operator aussehen und sich wie ein Operator verhalten.

Und wir haben Unterstützung für Infix-Operatoren, die mit Unicode definiert sind.
seit https://github.com/JuliaLang/julia/issues/552

Ich denke, es könnte schön sein, das zu erweitern, damit Sie die Schlüsselwörter wie im ursprünglichen Vorschlag hinzufügen können.
So könnten wir zum Beispiel 1 ⊕₂ 1 == 0 haben
Die Möglichkeit, beliebige Namen für Ihr Infix zu haben, erscheint etwas übertrieben.

sollte wie ein Operator aussehen und handeln.

Ich stimme zu, dass es strenge Namenskonventionen für Infix-Operatoren geben sollte. Zum Beispiel: ein Unicode-Zeichen oder endet auf einer Präposition. Aber das sollten Konventionen sein, die sich organisch entwickeln, keine vom Compiler erzwungenen Anforderungen. Sicherlich denke ich nicht, dass #552 das Ende der Geschichte ist; Wenn es Dutzende von fest codierten Operatoren gibt, sollte es eine Möglichkeit geben, weitere programmgesteuert hinzuzufügen, und sei es nur für das Prototyping neuer Funktionen.

...

Für mich ist der (a f b) (und (a <strong i="10">@m</strong> b) ) Vorschlag den anderen Vorschlägen in diesem Fehler weit überlegen. Ich bin fast versucht, einen Patch zu machen.

(a f b) => f(a,b)
(a f b c d) => f(a,b,c,d)
(a f) =>Syntaxfehler
(a+2 f+2 b+2) => (f+2)(a+2,b+2)
(t1=a t2=f t3=b) => (t1=f)((t2=a),(t3=b)) (Leerzeichen haben den geringstmöglichen Vorrang, wie in Makros)

...

Wäre es unangebracht, einen Patch einzureichen?

Ich habe die letzten beiden Fälle nicht verstanden:

(a+2 f+2 b+2)=>(f+2)(a+2,b+2)
(t1=a t2=f t3=b)=>(t1=f)((t2=a),(t3=b))

Ich finde die (a f b c d) -Syntax sehr seltsam. Da 1 + 2 + 3 als +(1,2,3) geschrieben werden kann, sollte f(a,b,c) dann nicht als (a f b f c) geschrieben werden?

Insgesamt bin ich persönlich nicht davon überzeugt, dass Julia benutzerdefinierte Infix-Operatoren über das derzeit Erlaubte hinaus unterstützen sollte.

Ich sehe zwei Probleme mit (a f b c d) .

Erstens ist es schwierig zu lesen, wenn Sie einen komplizierteren Ausdruck haben - einer der Gründe, warum Klammern frustrierend sein können, ist, dass es oft schwierig ist, auf einen Blick zu erkennen, welche Klammern mit welchen anderen Klammern zusammenpassen. Aus diesem Grund sind Infix- und Postfixing-Operatoren ( |> ) in erster Linie wünschenswert. Postfixing ist besonders beliebt, weil es ein schönes, ordentliches Lesen von links nach rechts ermöglicht, ohne jedes Mal mit Klammern umgehen zu müssen.

Zweitens lässt es keine Möglichkeit, Dinge wie es elementweise zu machen, gut zu machen. Mein Verständnis ist, dass f.(a,b) eine Notation in 0.5 sein wird, um f dazu zu bringen, elementweise auf seinen Argumenten mit Broadcasting zu arbeiten. Es gibt keinen sauberen Weg, dasselbe mit der Infix-Notation zu tun, wenn es (a f b) ist. Bestenfalls müsste es (a .f b) sein, was meiner Ansicht nach die Symmetrie verliert, die .( mit .+ und .* $ bietet.

Vergleichen Sie beispielsweise den Fall, das Beispiel von Haskell verwenden zu wollen. shashi auf #6946 hat darauf hingewiesen, dass es hier eine Entsprechung gibt. In Haskell würden Sie circle 10 |> move 0 0 |> animate "scale" "ease" schreiben. Mit dieser Schreibweise wird dies zu ((circle(10) move 0 0) animate "scale" "ease") , was nicht klarer ist als animate(move(circle(10),0,0),"scale","ease") . Und wenn Sie Ihren Kreis mit der |> -Notation an mehrere Orte kopieren möchten, haben Sie möglicherweise circle 10 .|> copy [1 15 50] [3 14 25] . Meiner Ansicht nach ist dies der sauberste Weg, um die Idee umzusetzen - und dann erfüllen Klammern ihre normale Rolle bei der Behandlung von Problemen mit der Reihenfolge der Operationen.

Und wie ich bereits erwähnt habe, hat a|>f b c den Vorteil, dass es auch eine natürliche Erweiterung hat, die es ermöglicht, dieselbe Notation mehr zu verwenden - f b c würde als "Funktion f mit Parameter b und c gesetzt) ​​und wäre somit äquivalent zu i->f(i,b,c) . Dadurch funktioniert es nicht nur zum Infixieren, sondern auch für andere Situationen, in denen Sie möglicherweise übergehen möchten eine Funktion (insbesondere eine eingebaute Funktion) mit Parametern (beachten Sie, dass der Standard darin besteht, zuerst das Objekt der Funktion zu haben).

Das |> macht auch deutlich, welches die Funktion ist. Wenn Sie beispielsweise (tissue wash fire dirty metal) hätten, wäre es ziemlich schwierig, auf einen Blick wash als Funktion zu erkennen. Auf der anderen Seite hat tissue|>wash fire dirty metal einen großen Indikator mit der Aufschrift „ wash ist die Funktion“.

Einige der jüngsten Einwände klingen für mich wie "Aber Sie könnten diese Funktion missbrauchen!" Meine Antwort ist: Natürlich könnten Sie. Sie könnten bereits völlig unlesbaren Code mit Makros schreiben, wenn Sie wollten. Die Aufgabe des Parsers besteht darin, legitime Verwendungen zu ermöglichen; Um Missbrauch zu stoppen, haben wir Konventionen/Redewendungen und in einigen Fällen Delinters. Speziell:

Ich habe die letzten beiden Fälle nicht verstanden:

Diese sind in keiner Weise als Beispiel gedacht; sie zeigen nur die natürlichen Folgen der Vorrangregeln. Ich denke, die beiden letzten Beispiele würden als Missbrauch der Syntax gelten, obwohl (a^2 ಠ_ಠ b^2) => ಠ_ಠ(a^2,b^2) klar genug ist.

sollte f(a,b,c) nicht als (afbfc) geschrieben werden

Mein Vorschlag von (a f b c d) war, ehrlich gesagt, ein nachträglicher Einfall. Ich halte es für sinnvoll, und ich könnte Beispiele nennen, wo es nützlich ist, aber ich möchte diesen Vorschlag zu diesem Thema nicht aufhängen, wenn er kontrovers ist.

[Zum Beispiel:

  • f ist eine "Objektmethode" eines Objekts a, wahrscheinlich kompliziert, unter Verwendung von b, c und d, wahrscheinlich einfacher.
  • f ist eine "natürlich übertragene" Methode wie push! ]

Während (a f b f c) sinnvoll wäre, wenn f wie + wäre, denke ich, dass die meisten Operatoren nicht wirklich wie + sind, also sollte es nicht unser Modell sein.

Es wird schwierig zu lesen sein, wenn Sie einen komplizierteren Ausdruck haben

Auch hier wäre meine Antwort: "Also missbrauche es nicht".

Angenommen, wir möchten einen komplizierten Ausdruck wie a / (b + f(c,d^e)) mit f Infix schreiben. Im Vorschlag von @toivoh wären das a / (b + (c f d^e)) . In Haskell-ähnlicher Verwendung wäre es a / (b + (c |> f d^e)) oder bestenfalls, wenn die Priorität |> geändert würde, um dieses eine bestimmte Beispiel zu beheben, a / (b + c |> f d^e) . Ich denke, dass @toivoh 's hier genauso gut ist.

(tissue wash fire dirty metal)

Ich denke, die Lösung dafür sind strenge Namenskonventionen für Infix-Operatoren. Wenn es beispielsweise eine Konvention gäbe, dass Operatoren ein Zeichen von Unicode einfügen oder auf eine Präposition oder einen Unterstrich enden sollten, dann wäre das obige so etwas wie (tissue wash_ fire dirty metal) , was so klar ist, wie dieser Ausdruck jemals sein könnte .

...

elementweise

Dies ist eine berechtigte Sorge. (a .f b) ist eine schlechte Idee, da es als ((a.f) b) gelesen werden könnte. Mein erster Vorschlag ist (a ..f b) , aber es macht mich nicht sehr glücklich.

circle 10 |> move 0 0 |> animate "scale" "ease"

Ich habe jquery verwendet, daher sehe ich definitiv den Vorteil einer solchen Funktionsverkettung. Aber ich denke, dass es nicht dasselbe Problem wie bei Infix-Operatoren ist. Unter Verwendung des (a f b) -Vorschlags könnten Sie das obige wie folgt schreiben:

circle 10 |> (move <| 0 0) |> (animate <| "scale" "ease")

... die nicht ganz so knapp wie die Haskell-Version ist, aber dennoch gut lesbar.

Vielleicht kann es auf nur drei Dinge in () beschränkt werden:
(a f (b,c))
.(a f (b,c)) mit dem Operator .(

Abschließend eine Antwort auf den allgemeinen Punkt:

Insgesamt bin ich persönlich nicht davon überzeugt, dass Julia benutzerdefinierte Infix-Operatoren über das derzeit Erlaubte hinaus unterstützen sollte.

Natürlich haben wir alle das Recht auf unsere Meinung. (Mir ist nicht klar, ob sich der Daumen nach oben auf diesen Teil des Kommentars bezog, aber wenn ja, geht das dreifach.)

Aber meine Gegenargumente sind:

  • Julia hat bereits Dutzende von Infix-Operatoren, von denen viele extrem nischenhaft sind. Es ist unvermeidlich, dass weitere vorgeschlagen werden. Wenn jemand sagt "wie können Sie haben, aber nicht § ?", würde ich viel lieber antworten "mach es selbst" und nicht "warten, bis die nächste Version weit verbreitet ist".
  • Etwas wie (a § b) ist hervorragend lesbar, und die Syntax ist leicht genug, um aus einem oder zwei Beispielen zu lernen.
  • Ich bin nicht die erste Person, die dieses Problem anspricht, und ich werde nicht die letzte sein. Ich verstehe, dass Sprachdesigner sehr, sehr skeptisch gegenüber schleichenden (Fehl-)Features sein sollten, denn sobald Sie ein hässliches Feature hinzugefügt haben, ist es im Grunde unmöglich, es später zu beheben. Aber wie ich oben sagte, denke ich, dass (a f b) sauber genug ist, dass Sie es nicht bereuen werden.

Ich bin mir wirklich nicht sicher über die Klarheit von (a f b)

Hier ist ein möglicher Anwendungsfall:
select((((:emp_id, :last_name) from employee_tbl) where (:city, == ,'indianapolis')) orderby :emp_id));

Dies ist sicherlich eine sinnvolle Verwendung von Infix-Funktionen.
Die Funktion select ist entweder die Identitätsfunktion oder sendet die erstellte Abfrage an die Datenbank.

Ist das ein eindeutiger Code?
Ich weiß es einfach nicht.

.(a f b)

Ja, das macht Sinn. Aber es ist nicht sehr lesenswert.

Ist (a @. f b) besser lesbar? Weil das Makro @. , um das zu aktivieren, ein einfacher Einzeiler wäre.

[[[Wenn ich darüber nachdenke, wenn wir Infix-Makros erlauben würden, ohne Klammern zu benötigen, könnte @Glen-O sie verwenden, um zu tun, was er will: circle(10) @> move 0 0 @> animate "scale" "ease" => @> (@> circle(10) move 0 0) animate "scale" "ease" =makro> animate(move(circle(10),0,0),"scale","ease") . Ich denke, diese Lösung ist hässlicher als (a f b) , aber zumindest würde sie diesen allgemeinen Fehler in meinen Augen beheben.]]]

...

select((((:emp_id, :last_name) from employee_tbl) where (:city, = ,'indianapolis')) orderby :emp_id);

Ich würde auf jeden Fall lieber ein Makro für "wo" verwenden, damit der bedingte Ausdruck nicht seltsam zitiert werden muss. So:

select((((:emp_id, :last_name) from employee_tbl) <strong i="22">@where</strong> city == 'indianapolis') orderby :emp_id);

Die Klammern sind leicht nervig, aber andererseits sehe ich keinen vernünftigen Weg für den Parser, mit dieser Art von Ausdruck ohne sie umzugehen.

select((((:emp_id, :last_name) from employee_tbl) <strong i="6">@where</strong> city == 'indianapolis') orderby :emp_id);

Die Klammern sind leicht nervig, aber andererseits sehe ich keinen vernünftigen Weg für den Parser, mit dieser Art von Ausdruck ohne sie umzugehen.

Bei genauerem Nachdenken ist der Vorrang in diesem Ausdruck von rechts nach links. Unter Verwendung von Infix-Makros könnte es also genauso gut sein:

select((:emp_id, :last_name) <strong i="11">@from</strong> employee_tbl <strong i="12">@where</strong> city == 'NYC' <strong i="13">@orderby</strong> :emp_id)

oder auch:

<strong i="17">@select</strong> (:emp_id, :last_name) <strong i="18">@from</strong> employee_tbl <strong i="19">@where</strong> city == 'NYC' <strong i="20">@orderby</strong> :emp_id

Obwohl ich immer noch (a f b) mag, sehe ich allmählich, dass Infix-Makros auch eine gute Antwort sind.

Hier ist der vollständige Vorschlag anhand von Beispielen, gefolgt von den Vor- und Nachteilen:

Hauptanwendungen:

  • a <strong i="28">@m</strong> b => <strong i="30">@m</strong> a b
  • a <strong i="33">@m</strong> b c => <strong i="35">@m</strong> a b c
  • a <strong i="38">@m</strong> b <strong i="39">@m2</strong> c => <strong i="41">@m2</strong> (<strong i="42">@m</strong> a b) c
  • <strong i="45">@defineinfix</strong> f; a <strong i="46">@f</strong> b => macro f(a,b...) :(f($a,$b...)) end; <strong i="48">@f</strong> a b => f(a,b)

Eckfälle: (nicht als guter Code gedacht, nur um zu zeigen, wie der Parser funktionieren würde)

  • t1=a <strong i="54">@m</strong> t2=b t3=c => <strong i="56">@m</strong> t1=a t2=b t3=c (obwohl dies kein guter Programmierstil ist)
  • t1 + a <strong i="59">@m</strong> t2 + b => <strong i="61">@m</strong> t1+a t2+b (obwohl dies kein guter Programmierstil ist)
  • a b <strong i="64">@m</strong> c => Syntaxfehler (??)
  • a <strong i="67">@m</strong> b [c,d] => bitte nicht, aber <strong i="70">@m</strong> a b[c,d] (ETA: Nein, mit dem Patch kommt das als <strong i="72">@m</strong> a b ([c,d]) heraus, was wahrscheinlich besser ist.)
  • a <strong i="75">@m</strong> b ([c,d]) => <strong i="77">@m</strong> a b ([c,d])
  • [a <strong i="80">@m</strong> b] => schlechter Stil, bitte verwenden Sie Klammern zur Verdeutlichung, aber [a (<strong i="83">@m</strong> b)] (??)
  • a @> f b => @> a f b => f(a,b)
  • <strong i="90">@outermacro</strong> a b <strong i="91">@m</strong> c d => <strong i="93">@outermacro</strong> a (<strong i="94">@m</strong> b c d)

Vorteile:

  • Definieren Sie Infix-Makros, erhalten Sie Infix-Funktionen kostenlos (mit einmaligem Overhead der Makro-Evaluierung. Das ist nicht ganz so wenig Overhead wie Parser-Magie, aber viel besser als zusätzliche Funktionsaufrufe bei jeder Evaluierung)
  • kann zu leistungsstarken DSLs führen, wie im obigen SQL-ähnlichen Beispiel zu sehen ist
  • Macht einen separaten |> -Operator überflüssig, da es sich um ein Einzeiler-Makro handelt. Ähnlich für <| und den Rest der Vorschläge von @Glen-O.
  • explizit, daher sehr geringes Risiko, versehentlich verwendet zu werden, im Gegensatz zu (a f b)
  • Wie gezeigt, könnte das @defineinfix -Makro eine Kurzschrift für Funktionen und nicht für Makros ermöglichen.

(Kleine) Nachteile:

  • Vorrang und Gruppierung scheinen in den meisten Fällen mit RtoL gut zu funktionieren, aber es würde Ausnahmen geben, die Klammern erfordern würden.
  • Ich denke, dass a @> f b oder sogar a <strong i="112">@f</strong> b nicht ganz so lesbar sind wie (a f b) (obwohl sie auch nicht allzu schrecklich sind).

Angesichts der Tatsache, wie aktiv dieser Thread geworden ist, werde ich die Leute an meine ursprüngliche Sorge zu diesem Thema erinnern: Probleme mit der Syntax erzeugen oft eine riesige Menge an Aktivität, aber diese Menge an Aktivität steht im Allgemeinen in keinem Verhältnis zum langfristigen Wert der diskutierten Änderung. Das liegt zum großen Teil daran, dass Threads über Syntax am Ende reinen Diskussionen über Geschmack nahe kommen.

diese Menge an Aktivität ist im Allgemeinen unverhältnismäßig

Es tut mir leid. Ich bin wahrscheinlich am schuldigsten, in ein Hin und Her zu geraten.

Andererseits denke ich, dass dieser Thread eindeutig "brauchbare" Fortschritte gemacht hat. Einer der neuesten Vorschläge (a f b) oder [ a @> f b , wobei a <strong i="10">@f</strong> b als Abkürzung definierbar ist] ist meiner Meinung nach den früheren Vorschlägen wie a %f% b oder deutlich überlegen a |> f <| b .

Dennoch denke ich, dass weiteres Hin und Her wahrscheinlich keine weiteren Fortschritte machen wird, und ich würde die Leute ermutigen, von nun an Daumen hoch oder Daumen runter zu verwenden, es sei denn, sie haben etwas wirklich Neues vorzuschlagen (das ist, nicht nur eine orthografische Änderung an einem bestehenden Vorschlag). Ich habe "Hurra"-Emoticons (explodierender Kegel) zu den "Votable Proposals" hinzugefügt. Wenn Sie der Meinung sind, dass wir keine spezialisierte Syntax für beliebige Funktionen in Infix-Position haben sollten, dann lehnen Sie den Fehler als Ganzes ab.

...

ETA: Ich denke, dass diese Diskussion jetzt reif genug ist, um ein decision -Tag zu bekommen.

Als Referenz (und ich erwartete, dass jemand anderes darauf hinweist).
Wenn Sie eine SQL-ähnliche Syntax einbetten möchten, ist meiner Meinung nach das richtige Werkzeug für diesen Job Nonstandard String Literals.
Wie alle Makros haben sie beim Aufruf Zugriff auf alle Variablen im Geltungsbereich,
und sie ermöglichen es Ihnen, Ihre eigene DSL mit Ihrer eigenen Wahl der Priorität anzugeben, und sie werden zur Kompilierzeit ausgeführt.

select((((:emp_id, :last_name) from employee_tbl) where (:city, == ,"indianapolis")) orderby :emp_id));

Ist besser geschrieben

sql"SELECT emp_id, last_name FROM employee_tbl WHERE city == 'indianapolis' ORDER BY emp_id"

Nicht standardmäßige Zeichenfolgenliterale sind ein wirklich leistungsfähiges Stück Syntax.
Ich kann keine guten Beispiele dafür finden, wie sie zum Einbetten einer DSL verwendet werden.
Aber sie können es tun.

Und in diesem Fall denke ich, dass das Ergebnis viel sauberer ist als jede Infix-Operation, die definiert werden kann.
Obwohl es den Aufwand hat, einen eigenen Mikroparser / Tokenizer schreiben zu müssen.


Ich sehe wirklich keine Notwendigkeit für ein Entscheidungs-Tag.
Dies hat weder eine Implementierung als PR noch einen verwendbaren Prototyp.
das lässt Leute es ausprobieren.
Im Gegensatz zu https://github.com/JuliaLang/julia/issues/5571#issuecomment -205754539 mit seinen 8 verwendbaren Prototypen

Meine Gefühle dazu gehen jedes Mal auf und ab, wenn ich den Thread lese. Ich glaube nicht, dass ich es wirklich wissen werde, bis ich es probiere. Und im Moment weiß ich nicht einmal, wofür ich es verwenden würde. (Im Gegensatz zu einigen Definitionen für |> und <| , die ich in F# verwendet habe)

SQL-ähnliche Syntax, das richtige Werkzeug für diesen Job sind Nonstandard String Literals

Unabhängig davon, ob SQL am besten mit NSLs ausgeführt wird oder nicht, ich denke, es gibt eine Ebene von DSL, die komplex genug ist, dass Inline-Makros sehr hilfreich wären, aber nicht so komplex, dass es sich lohnt, einen eigenen Mikroparser / Tokenizer zu schreiben.

Ich wüsste im Moment noch nicht einmal, wofür ich es verwenden würde. (Im Gegensatz zu einigen Definitionen für |> und <|, die ich in F# verwendet habe)

Der Inline-Makrovorschlag würde es Leuten unter anderem ermöglichen, ihre eigenen |> -ähnlichen oder <| -ähnlichen Makros zu erstellen, sodass Sie sie für alles verwenden könnten, was Sie in F# getan haben.

(Ich möchte nicht in die Hin- und Her-Argumente zum Bikeshedding geraten, aber ich habe trotzdem aus den folgenden Gründen geantwortet, und ich denke, dass der Inline-Makro-Vorschlag mehrere Fliegen mit einem relativ glatten Stein tötet.)

Ich sehe wirklich keine Notwendigkeit für ein Entscheidungs-Tag.

Ich habe vorhin gefragt, ob es für mich angebracht ist, einen Parser-Patch zu erstellen, und niemand hat geantwortet. Das einzige Wort dazu ist bisher:

Ich glaube nicht, dass es vor Julia 1.0 viel an "syntaktischer Innovation" geben wird.

Was gegen das Erstellen eines Patches zu sprechen scheint, da es einfach herumsitzen und verrotten könnte. Allerdings sagen Sie jetzt, dass es sich nicht lohnt, eine Entscheidung darüber zu treffen (einschließlich der Entscheidung, jetzt nicht zu entscheiden?), es sei denn, wir haben eine "Umsetzung als PR [oder] brauchbaren Prototyp".

Was bedeutet das? (Was ist ein PR?) Würde ein Makro, das das Zeichen '@' anstelle des Tokens @ verwendet, die Aufgabe erfüllen, sodass <strong i="22">@testinline</strong> a '@'f b => @f(a, b) ? Oder sollte ich einen Patch an julia-parser.scm senden? (Ich habe tatsächlich damit begonnen, einen solchen Patch zu schreiben, und es sieht so aus, als ob es einfach sein sollte, aber mein Schema ist sehr eingerostet.) Muss ich Testfälle erstellen?

Im Moment gibt es 13 Teilnehmer an diesem Fehler. Es gibt insgesamt 5 Personen, die über einen oder mehrere der Vorschläge abgestimmt und/oder den Fehler selbst abgelehnt haben, und nur einer von ihnen (ich) tat dies, nachdem der Inline-Makro-Vorschlag auf dem Tisch lag. Das stimmt mich nicht zuversichtlich, dass es jetzt schon Zeit für das Prototyping ist. Wenn die Zahl der Leute, die seit dem letzten ernsthaften Vorschlag abgestimmt haben, eher halb so hoch ist wie die Zahl der Teilnehmer, wird sich hoffentlich eine Art grober Konsens herauskristallisieren, und dann wird es Zeit für Prototypen und Tests und Entscheidungen (oder so weiter). der Fall kann sein, die Idee aufzugeben).

Durch „Umsetzung als PR [oder] nutzbarer Prototyp“.
Ich meine etwas, womit gespielt werden kann.
So kann man sehen, wie es sich in der Praxis anfühlt.

Ein PR ist ein Pull-Request, also ist ein Patch der Begriff, den Sie verwendet haben.

Wenn Sie eine PR erstellt haben, können Sie sie herunterladen und testen.
Einfacher jedoch, wenn Sie es mit Makros implementieren
oder Nonstandard-String-Literale,
es konnte getestet werden, ohne julia bauen zu müssen.

Als wäre es nicht meine Entscheidung, aber ich bezweifle, dass ich ohne etwas, mit dem ich spielen kann, in der Lage sein werde, mir eine eigene Meinung zu bilden.

Auch +1, um nicht zum Fahrradabwurf hin und her zu gehen.

... oder vielleicht ein Infix.jl -Paket mit Makros und nicht standardmäßigen Zeichenfolgenliteralen.

Wir haben in diesem Gespräch definitiv den Punkt "funktionierender Code oder GTFO" erreicht.

OK, hier ist dann der funktionierende Code: https://github.com/jamesonquinn/JuliaParser.jl

ETA: Soll ich auf einen bestimmten Commit verweisen oder ist der obige Link zum neuesten Master in Ordnung?

...

(Das hat keines der Convenience-Makros, die Sie erwarten würden, wie die Entsprechungen für |> , <| , ~ und die @defineinfix aus meinem obigen Beispiel. Es entfernt auch nicht die jetzt nutzlose Sonderfalllogik für ~ oder den |> -Operator _deprecate_ . Es ist nur der Parser ändert sich, damit es funktioniert getestete Grundfunktionalität, aber nicht alle Eckfälle.

...

Ich denke, dass der aktuelle hässliche Hack mit ~ zeigt, dass es einen klaren Anwendungsfall für so etwas gibt. Mit diesem Patch würden Sie @~ sagen, wenn Sie Makroverhalten benötigen; viel sauberer, ohne Sonderfall. Oder glaubt jemand ernsthaft, dass ~ absolut einzigartig ist und niemand das jemals wieder tun möchte?

Beachten Sie, dass der Patch (es ist noch kein PR, weil er auf den nativen Bootstrapped-Parser abzielt, aber im Moment das Schema, das man in Bezug auf PRs an erster Stelle setzen sollte) allgemein nützlicher ist als der Name des Problems hier. Der Name des Problems lautet "benutzerdefinierte Infix-Operatoren". Der Patch enthält Infix-Makros, wobei Infix-Operatoren nur als Nebeneffekt auftreten.

Der Patch in seiner jetzigen Form ist keine bahnbrechende Änderung, aber ich gehe davon aus, dass, wenn dies der Plan würde, der nächste Schritt darin bestehen würde, die derzeit bestehenden ~ und |> zu verwerfen, was schließlich dazu führen würde brechende Änderungen.

...

Einige einfache Tests hinzugefügt.

11608 wurde mit einem ziemlich klaren Konsens geschlossen, dass viele von uns keine Infix-Makros wollen, und der eine aktuelle Fall von ~ -Parsing war ein Fehler (früh gemacht aus Gründen der R-Kompatibilität und aus keinem anderen besonders guten Grund). Wir beabsichtigen, es zu verwerfen und schließlich loszuwerden, haben es aber noch nicht getan (zusammen mit der Arbeit, die API für die Formelschnittstelle in JuliaStats-Paketen zu modifizieren).

Makros sind jetzt technisch generisch, aber ihre Eingabeargumente sind immer Expr , Symbol oder Literale. Sie sind also nicht wirklich auf neue Typen erweiterbar, die in Paketen definiert sind, wie es Funktionen (Infix oder andere) sind. Mögliche Anwendungsfälle für Infix-Makros werden besser durch Präfix-annotierte Makro-DSLs oder String-Literale bedient.

(Entschuldigung, dass ich zu früh gepostet habe; jetzt behoben.)

In #11608 sehe ich mehrere negative Argumente:

===

In was würde sich das Folgende verwandeln?
...
y = 0.0 @in@ x == 1.0 ? 1 @in@ 2 : 3 @in@ 4

Das wurde in dem Thread behandelt:

In solchen Fällen verwende ich immer Klammern ...

und

gleicher Präzedenzfall ... gelten, ohne Makros zu sein: 0.0 in 1 == 1.0 ? 2 in 2 : 3 in 4

===

mehr Funktionalität für Julia, die Menschen implementieren, warten, testen, verwenden lernen müssen usw.

was hier (teilweise) beantwortet (und unterstützt) wird von:

"Kopfschmerzen für Parser-Entwickler" ist die geringstmögliche Sorge.

===

Gibt es keine Möglichkeit für 2 Pakete, gleichzeitig Definitionen für denselben Makrooperator zu haben, die zusammen eindeutig in einer einzigen Benutzercodebasis verwendet werden könnten?

Dies ist ein interessanter Punkt. Wenn das Makro nur eine Funktion aufruft, haben wir natürlich die gesamte Verteilungsleistung der Funktion. Aber wenn es sich um ein echtes Makro handelt, wie bei ~ , dann ist es komplizierter. Ja, Sie könnten sich hackige Problemumgehungen vorstellen, wie den Versuch, es als Funktion aufzurufen und Fehler abzufangen, um es als Makro zu verwenden ... aber diese Art von Hässlichkeit sollte nicht gefördert werden.

Dies ist jedoch für jedes Makro ein ebenso großes Problem. Wenn zwei Pakete beide ein Makro exportieren, können Sie einfach nicht beide mit "using" haben.

Ist dies eher ein Problem mit Infix-Makros? Nun, es hängt davon ab, wofür die Leute sie am Ende verwenden:

  • Nur eine Möglichkeit, benutzerdefinierte Infix-Funktionen zu haben. In diesem Fall sind sie nicht schlechter als jede andere Funktion; Versand funktioniert einwandfrei.
  • Um andere Programmierstile zu verwenden, verwenden Sie Operatoren wie |> und <| , die @Glen-O oben bespricht. In diesem Fall werden sich meiner Meinung nach schnell gemeinsame Konventionen darüber entwickeln, was Makro was bedeutet, mit geringer Kollisionsgefahr.
  • Als Möglichkeit, DSLs für spezielle Zwecke zu erstellen, wie im obigen SQL-Beispiel. Ich denke, diese werden in bestimmten Kontexten verwendet und die Wahrscheinlichkeit einer Kollision ist nicht allzu groß.
  • Für Dinge wie R's ~ . Das sieht zunächst am problematischsten aus; in R wird ~ für verschiedene Dinge verwendet. Ich denke jedoch, dass es selbst dort überschaubar ist, mit etwas wie:

macro ~(a,b) :(~(:$a, quote($b))) end

Dann könnte die Funktion ~ basierend auf dem Typ der LHS abgesetzt werden, aber die RHS wäre immer ein Ausdruck. So etwas würde es ermöglichen, dass die Hauptverwendungen, die es in R hat (Regression und Graphik), koexistieren, d. h. korrekt versenden, obwohl es aus verschiedenen Paketen kommt.

(Hinweis: Das Obige wurde bearbeitet. Anfangs dachte ich, dass ein R-Ausdruck wie a ~ b + c die Bindung von b und c durch die faule Auswertung von R verwendet. Aber das tut es nicht t; b und c sind die Namen von Spalten in einem explizit übergebenen Datenrahmen, nicht Namen von Variablen im lokalen Gültigkeitsbereich, die somit implizit übergeben werden.)

===

Der einzige Weg nach vorne wäre hier, eine tatsächliche Implementierung zu entwickeln.

Was ich getan habe.

===

Makros sind jetzt technisch generisch, aber ihre Eingabeargumente sind immer Ausdr, Symbol oder Literale. Sie sind also nicht wirklich auf neue Typen erweiterbar, die in Paketen definiert sind, wie es Funktionen (Infix oder andere) sind.

Dies bezieht sich auf den obigen Punkt. Soweit ein Infix-Makro eine bestimmte Funktion aufruft, ist diese Funktion weiterhin auf normale Weise durch Dispatch erweiterbar. Insofern es keine bestimmte Funktion aufruft, tut es etwas Strukturelles/Syntaktisches (wie das, was |> jetzt tut), das nicht erweitert oder neu definiert werden sollte. Beachten Sie, dass die Tatsache, dass es sich um ein Makro handelt, auch dann nützlich sein kann, wenn es eine Funktion aufruft. Beispielsweise kann es einige seiner Argumente zitieren oder sie in Callbacks verarbeiten oder sogar gleichzeitig mit dem Namen und der Bindung einer Variablen interagieren, auf eine Weise, die ein direkter Funktionsaufruf nicht kann.

===

Mögliche Anwendungsfälle für Infix-Makros werden besser durch Präfix-annotierte Makro-DSLs oder String-Literale bedient.

Wie im verlinkten Thread erwähnt:

[Infix ist] einfacher zu analysieren (für Englisch und die meisten westlichen Sprecher), weil unsere Sprache so funktioniert. (Dasselbe gilt im Allgemeinen für Operatoren.)

Was zum Beispiel besser lesbar (und beschreibbar) ist:

select((:emp_id, :last_name) <strong i="8">@from</strong> employee_tbl <strong i="9">@where</strong> city == 'NYC' <strong i="10">@orderby</strong> :emp_id)

oder

send(orderby((<strong i="14">@where</strong> selectfrom((:emp_id, :last_name), employee_tbl) city == 'NYC'), :emp_id))

?

===

Endlich:

11608 wurde mit einem ziemlich klaren Konsens geschlossen

Sieht für mich ziemlich gleichmäßig aus, wobei "Wer wird die Arbeit machen" die entscheidende Stimme abgibt. Was jetzt zumindest teilweise strittig ist; Ich habe die Arbeit in JuliaParser gemacht und ich wäre bereit, sie in Scheme zu machen, wenn die Leute diese Idee mögen.

Dies ist mein letzter Beitrag in diesem Thread, es sei denn, es gibt positive Reaktionen auf meinen gehackten Juliaparser. Es ist nicht meine Absicht, meinen Willen aufzuzwingen; nur um meinen Standpunkt darzulegen.

Ich plädiere für Infix-Makros ( a <strong i="6">@m</strong> b => <strong i="8">@m</strong> a b ). Das heißt nicht, dass ich die Gegenargumente nicht kenne. Hier ist, wie ich das beste Argument dagegen zusammenfassen würde:

Sprachfeatures beginnen bei -100. Was bieten Infix-Makros, die das möglicherweise überwinden könnten? Naturgemäß gibt es nichts, was Sie mit Infix-Makros erreichen könnten, was nicht mit Präfix-Makros erreicht werden könnte.

Meine Antwort ist: Julia ist in erster Linie eine Sprache für MINT-Programmierer. Mathematiker, Ingenieure, Statistiker, Physiker, Biologen, Menschen mit maschinellem Lernen, Chemiker, Ökonometriker ... Und ich denke, die meisten dieser Leute wissen, wie nützlich eine gute Notation ist. Um ein Beispiel zu nennen, mit dem ich in der Statistik vertraut bin: Das Hinzufügen unabhängiger Zufallsvariablen entspricht dem Falten von PDFs oder sogar dem Falten von Ableitungen von CDFs, aber oft kann das Ausdrücken von etwas mit Ersterem um eine Größenordnung präziser und verständlicher sein als Letzteres .

Infix versus Präfix versus Postfix ist bis zu einem gewissen Grad Geschmackssache. Aber auch sachliche Gründe sprechen in vielen Fällen dafür, infix zu bevorzugen. Während Präfix und Postfix zu unverdaulichen Niederschlägen von Back-to-Back-Operatoren führen, wie diejenigen, die Forth-Programmierer wie deutsche Politiker klingen lassen, oder diejenigen, die Lisp-Programmierer wie eine Chomski-Karikatur klingen lassen, versetzt Infix die Operatoren in das, was oft am kognitivsten ist natürlichen Ort, so nah wie möglich an all ihren Operanden. Es gibt einen Grund, warum niemand Mathearbeiten in Forth schreibt und warum selbst deutsche Mathematiker beim Schreiben von Gleichungen Infix-Operatoren verwenden.

Ja, Infix-Makros könnten verwendet werden, um verschleierten Code zu schreiben. Bestehende Präfix-Makros sind jedoch genauso anfällig für Missbrauch. Wenn sie nicht missbraucht werden, können Infix-Makros zu viel klarerem Code führen.

  • (a+b <strong i="18">@choose</strong> b) schlägt binomial(a+b,b) ;
  • score ~ age + treatment schlägt linearDependency(:score, :(age + treatment)) ;
  • domSelect("#logo") @| css "color" "red" @| fadeIn "slow" <strong i="25">@thenApply</strong> addClass "dummy" schlägt die heilige Hölle aus addOneTimeEventListener(fadeIn(css(domSelect("#logo"),"color","red"),"slow"),"done",(obj,evt)->addClass(obj,"dummy")) .

Mir ist klar, dass dies nur Spielzeugbeispiele sind, aber ich denke, das Prinzip ist gültig.

Könnte das obige mit nicht standardmäßigen Zeichenfolgenliteralen durchgeführt werden? Nun, das zweite und dritte Beispiel würden als NSLs funktionieren. Aber das Problem mit NSLs ist, dass sie Ihnen zu viel Freiheit lassen: Wenn Sie nicht mit der jeweiligen Grammatik vertraut sind, gibt es keine Möglichkeit, sich sicher zu sein, was die Tokens einer NSL sind, geschweige denn ihre Operationsreihenfolge. Mit Infix-Makros haben Sie genug Freiheit, um alle oben genannten Beispiele auszuführen, aber nicht so viel, dass beim Lesen des "guten" Codes nicht klar ist, was die Token sind und wo die impliziten Klammern stehen.

Es braucht bestimmte Dinge, um von unbekannten Unbekannten zu bekannten Unbekannten verschoben zu werden. Und leider gibt es dafür keinen Mechanismus. Ihre Argumente brauchen eine Struktur, die es nicht gibt.

Jetzt, da <| rechtsassoziativ ist (#24153), funktioniert der anfängliche a |>op<| b -Vorschlag?

Ich habe ein Paket für den von Steven erwähnten Hack in https://github.com/JuliaLang/julia/pull/24404#issuecomment -341570934 erstellt:

Ich weiß nicht, wie viele potenzielle Infix-Operatoren davon betroffen sind, aber ich würde wirklich gerne <~ verwenden. Der Parser wird nicht kooperieren - selbst wenn ich die Dinge sorgfältig platziere, möchte er, dass a <~ b a < (~b) bedeutet.

<- hat ein ähnliches Problem.

Tut mir leid, wenn dies bereits in diesem oder einem anderen Problem behandelt wird, aber ich konnte es nicht finden.

Möglicherweise benötigen wir Leerzeichen in a < ~b ; Wir haben solche Regeln schon einmal hinzugefügt. Dann könnten wir <- und <~ als Infix-Operatoren hinzufügen.

Danke @JeffBezanson , das wäre toll! Handelt es sich um einen Sonderfall oder um eine allgemeinere Regel? Ich bin mir sicher, dass es einige Details in der Regel geben sollte, mehr Infix-Operatoren zuzulassen, klaren und vorhersehbaren Code zu geben und so wenig wie möglich vorhandenen Code zu beschädigen. Trotzdem danke ich für die Hilfe und die schnelle Antwort. Frohes neues Jahr!

Falls sich a <~ b von a < ~b unterscheidet, würde ich gerne a =+ 1 als Fehler (oder zumindest Warnung) sehen

Ich weiß, dass dies eine ziemlich alte Diskussion ist, und die gestellte Frage wurde vor einiger Zeit gestellt, aber ich dachte, dass es sich lohnt, sie zu beantworten:

Jetzt, da <| rechtsassoziativ ist (#24153), funktioniert der anfängliche a |>op<| b -Vorschlag?

Nein, leider hat |> immer noch Vorrang. Das durchgeführte Update macht es so, dass Sie, wenn Sie <|(a,b)=a(b) definieren, erfolgreich a<|b<|c können, um a(b(c)) zu erhalten ... aber das ist ein anderes Konzept.

Eingefroren während 2 Jahren, ein Kommentar und ein Commit vor 2 und 5 Tagen !

Siehe Dokument anpassbare binäre Operatoren f45b6be

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

tkoolen picture tkoolen  ·  3Kommentare

omus picture omus  ·  3Kommentare

yurivish picture yurivish  ·  3Kommentare

i-apellaniz picture i-apellaniz  ·  3Kommentare

StefanKarpinski picture StefanKarpinski  ·  3Kommentare