|>
) für JavaScript (Dieses Dokument verwendet %
als Platzhalter-Token für die Themenreferenz. Dies wird mit ziemlicher Sicherheit nicht die endgültige Wahl sein ; Einzelheiten finden Sie in der Diskussion zum Token-Bikeshedding.)
In der Umfrage „State of JS 2020“ lautete die vierthäufigste Antwort auf die Frage „Was fehlt Ihrer Meinung nach derzeit in JavaScript?“ war Pfeifenbetreiber . Warum?
Wenn wir in JavaScript aufeinanderfolgende Operationen (z. B. Funktionsaufrufe) für einen Wert ausführen, gibt es derzeit zwei grundlegende Stile:
Das heißt, three(two(one(value)))
versus value.one().two().three()
. Allerdings unterscheiden sich diese Stile stark in Lesbarkeit, Sprachverständlichkeit und Anwendbarkeit.
Der erste Stil, nesting , ist allgemein anwendbar – er funktioniert für jede Abfolge von Operationen: Funktionsaufrufe, Arithmetik, Array-/Objektliterale, await
und yield
usw.
Allerdings ist die Verschachtelung schwer zu lesen , wenn sie tief wird: Der Ausführungsfluss bewegt sich von rechts nach links , anstatt dass normaler Code von links nach rechts gelesen wird. Wenn es auf einigen Ebenen mehrere Argumente gibt, springt das Lesen sogar hin und her : Unsere Augen müssen nach links springen, um einen Funktionsnamen zu finden, und dann müssen sie nach rechts springen, um zusätzliche Argumente zu finden. Darüber hinaus kann die nachträgliche Bearbeitung des Codes mühsam sein: Wir müssen zwischen vielen verschachtelten Klammern die richtige Stelle zum Einfügen neuer Argumente finden.
Betrachten Sie diesen realen Code von React.
console . log (
chalk . dim (
`$ ${ Object . keys ( envars )
. map ( envar =>
` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
} ` ,
'node' ,
args . join ( ' ' ) ) ) ;
Dieser reale Code besteht aus tief verschachtelten Ausdrücken . Um den Datenfluss lesen zu können, müssen die Augen eines Menschen zunächst:
Finden Sie die Anfangsdaten (den innersten Ausdruck, envars
).
Und scannen Sie dann wiederholt von innen nach außen nach jeder Datentransformation, wobei jede einzelne entweder ein leicht zu übersehender Präfixoperator auf der linken Seite oder ein Suffixoperator auf der rechten Seite ist:
Object.keys()
(linke Seite),.map()
(rechte Seite),.join()
(rechte Seite),chalk.dim()
(linke Seite), dannconsole.log()
(linke Seite).Aufgrund der tiefen Verschachtelung vieler Ausdrücke (von denen einige Präfixoperatoren , einige Postfixoperatoren und einige Zirkumfixoperatoren verwenden) müssen wir sowohl die linke als auch die rechte Seite überprüfen, um den Kopf jedes Ausdrucks zu finden.
Der zweite Stil, Methodenverkettung , ist nur verwendbar, wenn der Wert über die als Methoden für seine Klasse festgelegten Funktionen verfügt. Dies schränkt seine Anwendbarkeit ein. Aber wenn es angewendet wird, ist es dank seiner Postfix-Struktur im Allgemeinen benutzerfreundlicher und einfacher zu lesen und zu schreiben. Die Codeausführung erfolgt von links nach rechts . Tief verschachtelte Ausdrücke werden entwirrt . Alle Argumente für einen Funktionsaufruf werden mit dem Namen der Funktion gruppiert . Und das spätere Bearbeiten des Codes zum Einfügen oder Löschen weiterer Methodenaufrufe ist trivial, da wir lediglich den Cursor an einer Stelle platzieren und dann mit der Eingabe oder dem Löschen einer zusammenhängenden Zeichenfolge beginnen müssten.
Tatsächlich sind die Vorteile der Methodenverkettung so attraktiv , dass einige beliebte Bibliotheken ihre Codestruktur gezielt verzerren , um eine stärkere Methodenverkettung zu ermöglichen. Das bekannteste Beispiel ist jQuery , die immer noch die beliebteste JS-Bibliothek der Welt ist. Das Kerndesign von jQuery ist ein einzelnes Überobjekt mit Dutzenden von Methoden darauf, die alle denselben Objekttyp zurückgeben, sodass wir die Verkettung fortsetzen können. Es gibt sogar einen Namen für diesen Programmierstil: Fluent Interfaces .
Leider ist die Methodenverkettung allein bei aller Fließfähigkeit nicht mit den anderen Syntaxen von JavaScript kompatibel: Funktionsaufrufe, Arithmetik, Array-/Objektliterale, await
und yield
usw. Daher bleibt die Anwendbarkeit der Methodenverkettung begrenzt .
Der Pipe-Operator versucht, die Bequemlichkeit und Einfachheit der Methodenverkettung mit der breiten Anwendbarkeit der Ausdrucksverschachtelung zu vereinen.
Die allgemeine Struktur aller Pipe-Operatoren ist value |>
e1 |>
e2 |>
e3 , wobei e1 , e2 , e3 alles Ausdrücke sind, die aufeinanderfolgende Werte als Parameter annehmen. Der |>
-Operator führt dann einen gewissen Zauber aus, um value
von der linken Seite in die rechte Seite zu „leiten“.
Fortsetzung dieses tief verschachtelten realen Codes von React:
console . log (
chalk . dim (
`$ ${ Object . keys ( envars )
. map ( envar =>
` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
} ` ,
'node' ,
args . join ( ' ' ) ) ) ;
…wir können es als solches entwirren , indem wir einen Pipe-Operator und ein Platzhalter-Token ( %
) verwenden, das für den Wert der vorherigen Operation steht:
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > `$ ${ % } `
| > chalk . dim ( % , 'node' , args . join ( ' ' ) )
| > console . log ( % ) ;
Jetzt kann der menschliche Leser schnell die ursprünglichen Daten finden (was der innerste Ausdruck war, envars
) und dann linear von links nach rechts jede Transformation der Daten lesen.
Man könnte argumentieren, dass die Verwendung temporärer Variablen die einzige Möglichkeit sein sollte, tief verschachtelten Code zu entwirren. Die explizite Benennung der Variablen jedes Schritts bewirkt, dass etwas Ähnliches wie bei der Methodenverkettung geschieht, mit ähnlichen Vorteilen wie beim Lesen und Schreiben von Code.
Verwenden Sie zum Beispiel unser vorheriges modifiziertes Beispiel aus der realen Welt von React:
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > `$ ${ % } `
| > chalk . dim ( % , 'node' , args . join ( ' ' ) )
| > console . log ( % ) ;
…eine Version mit temporären Variablen würde so aussehen:
const envarString = Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' ) ;
const consoleText = `$ ${ envarString } ` ;
const coloredConsoleText = chalk . dim ( consoleText , 'node' , args . join ( ' ' ) ) ;
console . log ( coloredConsoleText ) ;
Aber es gibt Gründe, warum wir in der realen Welt ständig auf tief verschachtelte Ausdrücke im Code des jeweils anderen stoßen und nicht auf Zeilen mit temporären Variablen. Und es gibt Gründe, warum die methodenkettenbasierten fließenden Schnittstellen von jQuery, Mocha usw. immer noch beliebt sind.
Es ist oft einfach zu mühsam und wortreich, Code mit einer langen Sequenz temporärer, einmal verwendbarer Variablen zu schreiben . Es ist wohl auch für einen Menschen mühsam und optisch störend, es zu lesen .
Wenn die Benennung eine der schwierigsten Aufgaben beim Programmieren ist, werden Programmierer die Benennung von Variablen zwangsläufig vermeiden , wenn sie deren Nutzen als relativ gering erachten.
Man könnte argumentieren, dass die Verwendung einer einzelnen veränderlichen Variablen mit einem kurzen Namen die Wortvielfalt temporärer Variablen verringern und ähnliche Ergebnisse wie mit dem Pipe-Operator erzielen würde.
Beispielsweise könnte unser vorheriges modifiziertes Beispiel aus der realen Welt von React wie folgt umgeschrieben werden:
let _ ;
_ = Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' ) ;
_ = `$ ${ _ } ` ;
_ = chalk . dim ( _ , 'node' , args . join ( ' ' ) ) ;
_ = console . log ( _ ) ;
Aber Code wie dieser ist im realen Code nicht üblich . Ein Grund dafür ist, dass sich veränderbare Variablen unerwartet ändern können, was zu stillen Fehlern führt, die schwer zu finden sind. Beispielsweise könnte in einem Abschluss versehentlich auf die Variable verwiesen werden. Oder es könnte versehentlich innerhalb eines Ausdrucks neu zugewiesen werden.
// setup
function one ( ) { return 1 ; }
function double ( x ) { return x * 2 ; }
let _ ;
_ = one ( ) ; // _ is now 1.
_ = double ( _ ) ; // _ is now 2.
_ = Promise . resolve ( ) . then ( ( ) =>
// This does *not* print 2!
// It prints 1, because `_` is reassigned downstream.
console . log ( _ ) ) ;
// _ becomes 1 before the promise callback.
_ = one ( _ ) ;
Dieses Problem würde beim Pipe-Operator nicht auftreten. Das Thementoken kann nicht neu zugewiesen werden und Code außerhalb jedes Schritts kann seine Bindung nicht ändern.
let _ ;
_ = one ( )
| > double ( % )
| > Promise . resolve ( ) . then ( ( ) =>
// This prints 2, as intended.
console . log ( % ) ) ;
_ = one ( ) ;
Aus diesem Grund ist Code mit veränderlichen Variablen auch schwerer zu lesen. Um festzustellen, was die Variable an einem bestimmten Punkt darstellt, müssen Sie den gesamten vorhergehenden Bereich nach Stellen durchsuchen , an denen sie neu zugewiesen wird.
Die Themenreferenz einer Pipeline hingegen hat einen begrenzten lexikalischen Geltungsbereich und ihre Bindung ist innerhalb ihres Geltungsbereichs unveränderlich. Eine unbeabsichtigte Umbelegung ist nicht möglich und der Einsatz in Verschlüssen ist sicher.
Obwohl sich auch der Topic-Wert mit jedem Pipeline-Schritt ändert, scannen wir nur den vorherigen Schritt der Pipeline, um ihm einen Sinn zu geben, was zu einem Code führt, der leichter zu lesen ist.
Ein weiterer Vorteil des Pipe-Operators gegenüber Sequenzen von Zuweisungsanweisungen (ob mit veränderlichen oder unveränderlichen temporären Variablen) besteht darin, dass es sich um Ausdrücke handelt.
Pipe-Ausdrücke sind Ausdrücke, die direkt zurückgegeben, einer Variablen zugewiesen oder in Kontexten wie JSX-Ausdrücken verwendet werden können.
Die Verwendung temporärer Variablen erfordert dagegen Anweisungsfolgen.
Pipelines | Temporäre Variablen |
---|---|
const envVarFormat = vars =>
Object . keys ( vars )
. map ( var => ` ${ var } = ${ vars [ var ] } ` )
. join ( ' ' )
| > chalk . dim ( % , 'node' , args . join ( ' ' ) ) ; | const envVarFormat = ( vars ) => {
let _ = Object . keys ( vars ) ;
_ = _ . map ( var => ` ${ var } = ${ vars [ var ] } ` ) ;
_ = _ . join ( ' ' ) ;
return chalk . dim ( _ , 'node' , args . join ( ' ' ) ) ;
} |
// This example uses JSX.
return (
< ul >
{
values
| > Object . keys ( % )
| > [ ... Array . from ( new Set ( % ) ) ]
| > % . map ( envar => (
< li onClick = {
( ) => doStuff ( values )
} > { envar } < / li >
) )
}
< / ul >
) ; | // This example uses JSX.
let _ = values ;
_ = Object . keys ( _ ) ;
_ = [ ... Array . from ( new Set ( _ ) ) ] ;
_ = _ . map ( envar => (
< li onClick = {
( ) => doStuff ( values )
} > { envar } < / li >
) ) ;
return (
< ul > { _ } < / ul >
) ; |
Es gab zwei konkurrierende Vorschläge für den Pipe-Operator: Hack-Pipes und F#-Pipes. (Davor gab es einen dritten Vorschlag für eine „intelligente Mischung“ der ersten beiden Vorschläge, der jedoch zurückgezogen wurde, da seine Syntax streng genommen eine Obermenge eines der Vorschläge darstellt.)
Die beiden Pipe-Vorschläge unterscheiden sich nur geringfügig darin, was die „Magie“ ist, wenn wir unseren Code buchstabieren, wenn wir |>
verwenden.
Beide Vorschläge verwenden bestehende Sprachkonzepte wieder: Hack-Pipes basieren auf dem Konzept des Ausdrucks , während F#-Pipes auf dem Konzept der unären Funktion basieren.
Piping- Ausdrücke und Piping- Unärfunktionen haben dementsprechend kleine und nahezu symmetrische Kompromisse .
In der Pipe-Syntax der Hack-Sprache ist die rechte Seite der Pipe ein Ausdruck , der einen speziellen Platzhalter enthält, der ausgewertet wird, wobei der Platzhalter an das Ergebnis der Auswertung des Ausdrucks auf der linken Seite gebunden ist. Das heißt, wir schreiben value |> one(%) |> two(%) |> three(%)
um value
durch die drei Funktionen weiterzuleiten.
Vorteil: Die rechte Seite kann ein beliebiger Ausdruck sein, und der Platzhalter kann überall hingehen, wo jeder normale Variablenbezeichner hingehen könnte, sodass wir ohne besondere Regeln zu jedem gewünschten Code weiterleiten können:
value |> foo(%)
für unäre Funktionsaufrufe,value |> foo(1, %)
für n-äre Funktionsaufrufe,value |> %.foo()
für Methodenaufrufe,value |> % + 1
für Arithmetik,value |> [%, 0]
für Array-Literale,value |> {foo: %}
für Objektliterale,value |> `${%}`
für Vorlagenliterale,value |> new Foo(%)
zum Konstruieren von Objekten,value |> await %
für das Warten auf Versprechen,value |> (yield %)
zum Ermitteln von Generatorwerten,value |> import(%)
zum Aufrufen funktionsähnlicher Schlüsselwörter, Nachteil: Die Weiterleitung durch unäre Funktionen ist mit Hack-Pipes etwas ausführlicher als mit F#-Pipes. Dazu gehören unäre Funktionen, die von Funktionsbibliotheken wie Ramda erstellt wurden, sowie unäre Pfeilfunktionen, die eine komplexe Destrukturierung ihrer Argumente durchführen: Hack-Pipes wären mit einem expliziten Funktionsaufrufsuffix (%)
etwas ausführlicher.
(Eine komplexe Destrukturierung des Topic-Werts wird einfacher, wenn Ausdrücke fortschreiten, da Sie dann Variablenzuweisungen/-destrukturierungen innerhalb eines Pipe-Körpers durchführen können.)
In der Pipe-Syntax der F#-Sprache ist die rechte Seite der Pipe ein Ausdruck, der in eine unäre Funktion ausgewertet werden muss, die dann stillschweigend mit dem Wert der linken Seite als einzigem Argument aufgerufen wird. Das heißt, wir schreiben value |> one |> two |> three
um value
durch die drei Funktionen weiterzuleiten. left |> right
wird zu right(left)
. Dies nennt man stillschweigende Programmierung oder punktfreien Stil.
Verwenden Sie zum Beispiel unser vorheriges modifiziertes Beispiel aus der realen Welt von React:
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > `$ ${ % } `
| > chalk . dim ( % , 'node' , args . join ( ' ' ) )
| > console . log ( % ) ;
…eine Version, die F#-Pipes anstelle von Hack-Pipes verwendet, würde so aussehen:
Object . keys ( envars )
. map ( envar => ` ${ envar } = ${ envars [ envar ] } ` )
. join ( ' ' )
| > x => `$ ${ x } `
| > x => chalk . dim ( x , 'node' , args . join ( ' ' ) )
| > console . log ;
Pro: Die Einschränkung, dass die rechte Seite in eine unäre Funktion aufgelöst werden muss , ermöglicht es uns, sehr knappe Pipes zu schreiben, wenn die Operation, die wir ausführen möchten, ein unärer Funktionsaufruf ist:
value |> foo
für unäre Funktionsaufrufe. Dazu gehören unäre Funktionen, die von Funktionsbibliotheken wie Ramda erstellt wurden, sowie unäre Pfeilfunktionen, die eine komplexe Destrukturierung ihrer Argumente durchführen: F#-Pipes wären mit einem impliziten Funktionsaufruf (no (%)
) etwas weniger ausführlich .
Nachteil: Die Einschränkung bedeutet, dass alle Operationen , die mit einer anderen Syntax ausgeführt werden, etwas ausführlicher gemacht werden müssen, indem die Operation in eine unäre Pfeilfunktion eingeschlossen wird :
value |> x=> x.foo()
für Methodenaufrufe,value |> x=> x + 1
für Arithmetik,value |> x=> [x, 0]
für Array-Literale,value |> x=> ({foo: x})
für Objektliterale,value |> x=> `${x}`
für Vorlagenliterale,value |> x=> new Foo(x)
zum Konstruieren von Objekten,value |> x=> import(x)
zum Aufrufen funktionsähnlicher Schlüsselwörter,Sogar der Aufruf benannter Funktionen erfordert einen Umbruch , wenn wir mehr als ein Argument übergeben müssen:
value |> x=> foo(1, x)
für n-äre Funktionsaufrufe. Nachteil: Die Vorgänge await
und yield
sind auf ihre enthaltende Funktion beschränkt und können daher nicht von unären Funktionen allein verarbeitet werden . Wenn wir sie in einen Pipe-Ausdruck integrieren wollen, müssen await
und yield
als spezielle Syntaxfälle behandelt werden:
value |> await
auf wartende Versprechen undvalue |> yield
zum Erzeugen von Generatorwerten. Sowohl Hack-Pipes als auch F#-Pipes erheben jeweils eine kleine Syntaxsteuer für verschiedene Ausdrücke:
Hack-Pipes belasten nur unäre Funktionsaufrufe , und geringfügig
F#-Pipes belasten leicht alle Ausdrücke außer unären Funktionsaufrufen.
In beiden Vorschlägen ist die Syntaxsteuer pro besteuertem Ausdruck gering ( beide (%)
und x=>
sind nur drei Zeichen ). Die Steuer vervielfacht sich jedoch mit der Verbreitung ihrer jeweils besteuerten Ausprägungen. Es könnte daher sinnvoll sein, eine Steuer auf die weniger gebräuchlichen Ausdrücke zu erheben und zugunsten der gebräuchlicheren Ausdrücke zu optimieren .
Unäre Funktionsaufrufe sind im Allgemeinen weniger verbreitet als alle Ausdrücke außer unären Funktionen. Insbesondere Methodenaufrufe und n-äre Funktionsaufrufe werden immer beliebt sein; Im Allgemeinen wird die Häufigkeit unärer Funktionsaufrufe allein in diesen beiden Fällen erreicht oder sogar überschritten – ganz zu schweigen von anderen allgegenwärtigen Syntaxen wie Array-Literalen , Objekt-Literalen und arithmetischen Operationen . Dieser Erklärer enthält mehrere Beispiele aus der Praxis für diesen Unterschied in der Prävalenz.
Darüber hinaus werden wahrscheinlich auch mehrere andere vorgeschlagene neue Syntaxen , wie z. B. Erweiterungsaufrufe , Do-Ausdrücke und Datensatz-/Tupelliterale , in Zukunft allgegenwärtig sein. Ebenso würden arithmetische Operationen noch häufiger vorkommen, wenn TC39 die Operatorüberladung standardisiert. Das Entwirren der Ausdrücke dieser zukünftigen Syntax wäre mit Hack-Pipes flüssiger als mit F#-Pipes.
Die Syntax von Hack-Pipes bei unären Funktionsaufrufen (d. h. (%)
zum Aufrufen der unären Funktion auf der rechten Seite) ist kein Sonderfall : Es handelt sich lediglich um das explizite Schreiben von gewöhnlichem Code , so wie wir es normalerweise ohne Pipe tun würden .
Andererseits erfordern F#-Pipes, dass wir zwischen „Code, der in eine unäre Funktion aufgelöst wird“ und „jedem anderen Ausdruck“ unterscheiden – und daran denken, den Pfeilfunktionswrapper um den letzteren Fall hinzuzufügen.
Beispielsweise ist bei Hack-Pipes value |> someFunction + 1
eine ungültige Syntax und schlägt vorzeitig fehl . Es besteht keine Notwendigkeit zu erkennen, dass someFunction + 1
nicht zu einer unären Funktion ausgewertet wird. Aber mit F#-Pipes ist value |> someFunction + 1
immer noch eine gültige Syntax – es schlägt nur spät zur Laufzeit fehl, weil someFunction + 1
nicht aufrufbar ist.
Die Pfeifen-Champion-Gruppe hat TC39 zweimal Fis-Pfeifen für Stufe 2 präsentiert. Beide Male gelang es nicht, zur Stufe 2 vorzudringen. Beide F#-Pipes (und die Partial Function Application (PFA)) sind aufgrund verschiedener Bedenken auf starken Widerstand mehrerer anderer TC39-Vertreter gestoßen. Dazu gehörten:
await
.Dieser Widerstand kam von außerhalb der Pipe-Champion-Gruppe. Weitere Informationen finden Sie unter HISTORY.md.
Die Pipe-Champion-Gruppe ist davon überzeugt, dass jeder Pipe-Operator besser ist als keiner, um tief verschachtelte Ausdrücke einfach zu linearisieren, ohne auf benannte Variablen zurückgreifen zu müssen. Viele Mitglieder der Champion-Gruppe glauben, dass Hack-Pfeifen etwas besser sind als F#-Pfeifen, und einige Mitglieder der Champion-Gruppe glauben, dass F#-Pfeifen etwas besser sind als Hack-Pfeifen. Aber alle in der Champion-Gruppe sind sich einig, dass F#-Pfeifen auf viel zu großen Widerstand gestoßen sind, als dass sie in absehbarer Zeit TC39 bestehen könnten.
Um es zu betonen: Es ist wahrscheinlich, dass ein Versuch, von Hack-Pipes zurück zu F#-Pipes zu wechseln, dazu führen wird, dass TC39 überhaupt keinen Pipes zustimmt. Auch die PFA-Syntax steht in TC39 vor einem harten Kampf (siehe HISTORY.md). Viele Mitglieder der Pipe-Champion-Gruppe halten dies für bedauerlich und sind bereit, später erneut für einen F#-Pipe-Split-Mix und eine PFA-Syntax zu kämpfen. Aber es gibt eine ganze Reihe von Vertretern (einschließlich Browser-Engine-Implementierern) außerhalb der Pipe Champion Group, die generell gegen die Förderung stillschweigender Programmierung (und PFA-Syntax) sind, unabhängig von Hack Pipes.
(Ein formeller Spezifikationsentwurf ist verfügbar.)
Der Themenverweis %
ist ein Nulloperator . Es fungiert als Platzhalter für einen Themenwert , hat einen lexikalischen Gültigkeitsbereich und ist unveränderlich .
%
ist keine endgültige Entscheidung (Das genaue Token für die Themenreferenz ist nicht endgültig . %
könnte stattdessen ^
oder viele andere Token sein. Wir planen, das tatsächlich zu verwendende Token auszutauschen , bevor wir mit Stufe 3 fortfahren. Allerdings scheint %
das syntaktisch am wenigsten problematische zu sein, und es ähnelt auch den Platzhaltern von Zeichenfolgen im printf-Format und #(%)
-Funktionsliteralen von Clojure .)
Der Pipe-Operator |>
ist ein Infix-Operator , der einen Pipe-Ausdruck (auch Pipeline genannt) bildet. Es wertet seine linke Seite (den Pipe-Kopf oder Pipe-Input ) aus, bindet den resultierenden Wert (den Topic-Wert ) unveränderlich an die Topic-Referenz und wertet dann seine rechte Seite (den Pipe-Körper ) mit dieser Bindung aus. Der resultierende Wert auf der rechten Seite wird zum Endwert des gesamten Pipe-Ausdrucks (der Pipe-Ausgabe ).
Der Vorrang des Pipe-Operators ist derselbe wie:
=>
;=
, +=
usw.;yield
und yield *
; Es ist enger als nur der Kommaoperator ,
.
Es ist lockerer als alle anderen Operatoren.
Zum Beispiel: v => v |> % == null |> foo(%, 0)
würde gruppieren in v => (v |> (% == null) |> foo(%, 0))
,
was wiederum äquivalent zu v => foo(v == null, 0)
ist.
Ein Pipe-Körper muss seinen Topic-Wert mindestens einmal verwenden. Beispielsweise ist value |> foo + 1
eine ungültige Syntax , da sein Hauptteil keinen Themenverweis enthält. Dieser Entwurf ist darauf zurückzuführen, dass das Weglassen der Themenreferenz im Hauptteil eines Pipe-Ausdrucks mit ziemlicher Sicherheit ein versehentlicher Programmierfehler ist.
Ebenso muss ein Themenverweis in einem Pipe-Körper enthalten sein. Die Verwendung einer Themenreferenz außerhalb eines Pipe-Körpers ist ebenfalls eine ungültige Syntax .
Um eine verwirrende Gruppierung zu verhindern, ist es eine ungültige Syntax, andere Operatoren mit ähnlicher Priorität (z. B. den Pfeil =>
, den ternären Bedingungsoperator ?
:
, die Zuweisungsoperatoren und den yield
-Operator) als Pipe-Kopf oder -Körper zu verwenden. Wenn wir |>
mit diesen Operatoren verwenden, müssen wir Klammern verwenden, um explizit anzugeben, welche Gruppierung richtig ist. Zum Beispiel a |> b ? % : c |> %.d
ist ungültige Syntax; Es sollte entweder zu a |> (b ? % : c) |> %.d
oder a |> (b ? % : c |> %.d)
korrigiert werden.
Schließlich können Themenbindungen innerhalb von dynamisch kompiliertem Code (z. B. mit eval
oder new Function
) nicht außerhalb dieses Codes verwendet werden. Beispielsweise löst v |> eval('% + 1')
einen Syntaxfehler aus, wenn der eval
zur Laufzeit ausgewertet wird.
Weitere Sonderregelungen bestehen nicht .
Ein natürliches Ergebnis dieser Regeln ist, dass wir, wenn wir einen Nebeneffekt in der Mitte einer Kette von Pipe-Ausdrücken einfügen müssen, ohne die durchgeleiteten Daten zu ändern, einen Komma-Ausdruck verwenden könnten, beispielsweise mit value |> (sideEffect(), %)
. Wie üblich wird der Komma-Ausdruck auf seiner rechten Seite ausgewertet %
, wobei er im Wesentlichen den Themenwert durchläuft, ohne ihn zu ändern. Dies ist besonders nützlich für schnelles Debuggen: value |> (console.log(%), %)
.
Die einzigen Änderungen gegenüber den Originalbeispielen waren die Herabstufung und Entfernung von Kommentaren.
Von jquery/build/tasks/sourceMap.js:
// Status quo
var minLoc = Object . keys ( grunt . config ( "uglify.all.files" ) ) [ 0 ] ;
// With pipes
var minLoc = grunt . config ( 'uglify.all.files' ) | > Object . keys ( % ) [ 0 ] ;
Von node/deps/npm/lib/unpublish.js:
// Status quo
const json = await npmFetch . json ( npa ( pkgs [ 0 ] ) . escapedName , opts ) ;
// With pipes
const json = pkgs [ 0 ] | > npa ( % ) . escapedName | > await npmFetch . json ( % , opts ) ;
Von underscore.js:
// Status quo
return filter ( obj , negate ( cb ( predicate ) ) , context ) ;
// With pipes
return cb ( predicate ) | > _ . negate ( % ) | > _ . filter ( obj , % , context ) ;
Von ramda.js.