Pipeline operators are an upcoming feature to Javascript which gives us another way to pass values through a series of transformations. It gives more context to what developers were trying to achieve when they wrote their code, and allow us to do some cool things to boot. Here, we'll take a quick look at pipeline operators, how they work, and how you can use them today.
Javascript Pipeline Operators: Support
Currently, no browser or server side ECMAScript implementation (like Node.JS) support pipeline operators. You can, however, get them to work using Babel 7.15. You can learn more about installing Babel here, but suffice to say this will allow you to add pipeline operators into your code.
Javascript Pipeline Operators: How they work
Pipeline operators are simply another way to manipulate values in Javascript. The pipeline operator is |>. Suppose we have 3 mathematical functions which add numbers to an input value:
// Adds 1 to a number
let addOne = function(x) {
return x + 1;
}
// Multiplies a number by 2
let multiplyByTwo = function(x) {
return x * 2;
}
// Divides a number by 6
let divideBySix = function(x) {
return x / 6;
}
If we wanted to apply all of these functions to a number we have, we might do something like this today:
let number = 6;
let calculate = addOne(multiplyByTwo(divideBySix(number)));
console.log(calculate); // Returns 3.
Although this works, when using multiple functions this can become quite messy - and often can take many lines. As such, we can simplify the above with a pipeline operator like so:
let number = 6;
let calculate = number |> divideBySix(%) |> multiplyByTwo(%) |> addOne(%);
console.log(calculate); // Returns 3.
As you can see, this simplifies processing of numbers and values so that we can clearly see what is happening. Let's break down what we have done:
First, we use number, and pass it with a pipe operator to divideBySix()
. We use %
to represent the value from before the pipe operator, in this case, 6 which is in our number variable.
Then we pass the number from divideBySix()
to multiplyByTwo()
. Again, we use % to represent the outcome of the previous operation, i.e. the value of the divideBySix()
function.
Finally, we do it again and addOne()
to our value. The outcome is the same, so we still get 3 at the end.
Simplifying Object Mapping with Pipeline Operators
Obviously the above example is a very simple application, but we can also use pipeline operators to do cooler things, like map arrays in a more coherent fashion. For instance, the below takes an object of URL queries, and merges them into a text string which can be added to the end of a URL:
let URLParams = {
'x' : '10245',
'linkId': 'eojff-efekv-ef0kw',
'author' : 'john-smith',
'featured' : false
}
let getURLQuery = Object.keys(URLParams).map((key) => `${key}=${URLParams[key]}`) |> %.join('&') |> `?${%}`;
// Returns ?x=10245&linkId=eojff-efekv-ef0kw&author=john-smith&featured=false
console.log(getURLQuery);
Conclusion on Javascript Pipeline Operators
Since pipe operators are not widely supported, you can only use this feature with Babel installed. With that said, pipeline operators add a lot of context to your code, and simplify operations so you can expand upon them later. As such, it may be worth giving Babel a try to get this into your code base. If you want to read the full pipeline operator specification, click here.
Top comments (19)
Can anyone explain benefit of this? Except for different syntax, there is no performance benefit. Almost same number of characters are required to write same logic it isn’t even smaller syntax except you can write each one in new line.
This way there are million different syntax offered by various language, bringing all in JavaScript will be complex.
In OO programming there is the concept of the fluent interface that allows you to chain method calls without needing intermediate assignments — this for example features prominently in the fluent builder patterm.
The disadvantage of this approach is that:
this
) andIn value-oriented programming (most people call it functional programming though that definition is actually narrower) computation is advanced through the "transformation of values" rather than "flow of control".
So with pipelining you can use the "fluent chaining style" on any value including Primitve Values and objects without the required method/function.
Another way of writing
is
The advantage with the second approach is that the function applications are now ordered left-to-right which many people find easier to read. The downside is the overhead of reducing an array of functions at runtime when the order and composition can fixed at design time ("Bake, don't fry"). Using the pipeline fixes the the reading order of the functions while not incurring any additional runtime overhead. Ideally the placeholder should be optional so that
could be replaced with
for single argument functions but I don't think that is even considered in the current proposal (ReScript has the advantage of resolving the placeholder at compile time; perhaps there are JS runtime performance concerns with an optional placeholder);
Perhaps you are forgetting that JavaScript started out as "doing Scheme in the browser" — making it vaguely look like Java was a marketing stunt.
Frankly that might simply be a result of your own familiarity bias. Most people who were taught imperative programming first, initially perceive functional programming as "less natural" and more difficult. But educator's experience has shown that is largely baby duck syndrome; in fact many students find it easier to start with functional programming first though there are always exceptions.
It should be no more difficult to follow the piped functions in the debugger or throw in a
tee
function.Being imperatively minded also means thinking predominantly in "statements" as computation is about "place-oriented" data manipulation and "flow of control". Value-oriented programming relies heavily on "expressions" instead as computation is expressed as "transformations of values" and the pipe operator expands the expression toolkit.
Was F# ever considered as an option especially given that ML languages are considered to be good for writing compilers?
I understand that
The issue is that there is already a precedent with Rhino. While it's still around, it seems to be languishing despite being based on something as "safe" and prevalent as Java. And Oracle has abandoned Nashorn in favour of supporting JS directly on GraalVM — removing Java as an intermediary.
Another approach would be to simply create a runtime that implements some smaller instruction set (like ES3) as generated by the Closure Compiler.
That said the pipeline operator proposal is only at Stage 2, with reservations as of August 2021 — so it may never happen and even in the best case wouldn't enter the spec until 2023 at the earliest.
I would imagine that Pattern Matching would cause a lot more work than the pipeline operator.
Thanks for detailed answer, I do find sequence of functions in correct order as important thing as nested function calls are evaluated from depth first order.
Basically its just for readability. In the example above:
It takes a bit to parse what is happening, and you have to work inside out. Using the pipeline operator you can make it read a bit more step by step, and you can chain a lot of functions together without all of the parens.
Parsing benefit is very small compared to feature overhead. Also for better debugging chaining functions should be avoided, instead minizers can chain functions to reduce code size. And what about multiple parameters? I see no difference in terms of parsing benefit and syntax in below both styles.
The later has better visibility and easy to debug. The reason I am asking as I have written JavaScript engine for .NET, and we are incorporating more features and I want to implement upcoming features as part of test drive.
I certainly want to know the real benefit, for example
??
has benefit over||
as??
will only check fornull or undefined
and||
will first callvalueOf
for objects and check if string is empty or not, leading to slower execution.?.
is not only a shorter syntax but faster execution as it will generate smaller machine instructions to duplicate value on stack without causing any overhead of storing value in a variable and evaluating it.yantrajs / yantra
JavaScript Engine for .NET Standard
YantraJS
Yantra (Machine in Sanskrit) is a Managed JavaScript Engine for .NET Standard written completely in C#.
NuGet
Documentation
Discussions
We recommend using Github Discussion on this repository for any question regarding this product.
Special Thanks
For the first two points, if you are writing JS in a functional style then chaining functions is used often to compose functionality. In those instances, having a single input function is more common.
As far as the second example, if you want to avoid mutations / shadowing, the reassigning variables is something to avoid.
Again, these are personal choices on how to write the JS code. I guess its just part of the challenges of having multiple paradigms in a language. Here is the full proposal though, it will hopefully give more insight into the rationale: github.com/tc39/proposal-pipeline-...
Its still in Stage 2 and there appears to be outstanding questions around it since August.
If you don’t want to wait you can use them right now with Elm, check them out at elm-lang.org/docs/syntax#operators … includes a lot of other cool things, specially a very helpful and fast compiler
ReScript also has a pipe operator
->
. Interestingly they split thepipeline
into two separate features — thepipe
and the optional pipe placeholder.Data-first and data-last: a comparison
Most of the functions in JS are n-ary (
n > 1
) and "Data-first". The "triangle pipe" has more utility in "Data-last" languages that support currying.BuckleScript Pipe Operator API Review
In the case of Elixir the pipe operator targets the first argument of the function as currying isn't supported.
Yes, ReScript is also fascinating !! … thank God we have better alternatives to just JavaScript or TypeScript !!
Yes, Elm is really cool !!
You can use elm-pages for having Elm with hydration, it's really cool, you may give it a check at -> elm-radio.com/episode/elm-pages-v2
and also at -> elm-pages.com/docs/what-is-elm-pages
elm-pages is not the only cool tool, you can use a lot more like the elm time travel for debugging and Elm UI for a good design and interface along with Tailwind CSS Elm modules ... the Elm ecosystem is fascinating and powerful nowadays
If you don’t want to wait you can use them right now with Elm, check them out at elm-lang.org/docs/syntax#operators … and includes a lot of other cool things, specially a very helpful and fast compiler
Yeees... 😁
Just a gentle jab. Anyway, there are other ways of achieving pipelining without the new Hack-pipe operator that we can use today.
Ouch !! … (just a gentle ouch)
Another gentle jab 😁 you could use the F# one directly as well
fable.io
Yes, Fable is great too !! 👍😎
Reminds me of Ocaml
TC39 Proposal readme:
So currently the Hack pipes proposal is the only one in the running (stage 2; no OCaml-style reverse-application operator).
Aside: Archeological Semiotics: The Birth of the Pipeline Symbol, 1994
Douglas McIlroy conceived the notion of the "pipe" which Ken Thompson added to Version 3 Unix in 1973. The
|
character wasn’t associated with it until Version 4 Unix in 1978.You can make them unary though, since one can't just take currying away.
If you really want that, you can just write a small (tunctional) library, and if you use Deno, you can import it straight from Git, or a cloud, or wherever.
Actually, I now have an urge to do just that after I'm done with work. If I don't pass out, that is.
This looks quite exciting and useful! Thanks for sharing.