Adventure operator pipeline in babel @ 7
- Transfer
In the release of babel@7.0.0-beta52, a new mandatory config flag for the plugin has appeared @babel/plugin-proposal-pipeline-operator
, which breaks backward compatibility for previous versions of the plugin. In this article you will learn what an operator is pipeline
and why it needs a configuration.
UPD: smart pipelines appeared in babel@7.2.0: https://babeljs.io/blog/2018/12/03/7.2.0#smart-pipeline-operator-parsing-8289-https-githubcom-babel-babel- pull-8289
Current status
Gilbert Garza , who originally proposed the operator pipeline
, aimed to get a simple syntax for "ordered chains of function calls in a readable functional style." The pipeline operator has its origin in languages such as F #, Hack, Elm, Elixir, and others, and when adding it to JavaScript, there are two controversial points:
- Where and how to use placeholders?
- How should work
async / await
in the pipeline?
Placeholders
The first question was raised by Kevin Smith in this ticket , where he suggested using the Hack style pipelines . In Hack, placeholders are required for any right side of the pipeline, for example:
namespace Hack\UserDocumentation\Operators\Pipe\Examples\MapFilterCountPiped;
function piped_example(array<int> $arr): int {
return $arr
|> \array_map($x ==> $x * $x, $$)
|> \array_filter($$, $x ==> $x % 2 == 0)
|> \count($$);
}
var_dump(piped_example(range(1, 10)));
We took as a basis that the placeholder can be used in any expression and contains the value from the last step of the pipeline. This approach gives us the flexibility and opportunities for the formulation of expressions.
The reverse side of the coin is the complication of the language due to the addition of a new token. So far, we have chosen the hash ( #
), and although the discussion is still open, any token will potentially have intersections with other uses. For example, the hash is also used by the private fields of the class , like any other token variants are used anyway .
Async / await
The first version of the operator pipeline
contained the following syntax for await
:
x |> await f
what can be expanded to:
await f(x)
Unfortunately, users may well expect this deployment:
(await f)(x)
While the idea of adding async
to the pipeline was skidding, members of the committee spoke out against the operator pipeline
who does not support async / await
. Yes, there are options for how to work with functions that return Promise without explicit syntax, but all of these options are too cumbersome or require auxiliary functions.
Proposed Solutions
As a result of the discussions, two proposals were formed (in addition to the minimum option): use F # pipelines and Smart pipelines. Let's look at these suggestions.
Minimal version of the operator
This offer only applies to basic functionality. In the minimal version, asynchronous support is removed and there are no placeholders. This option corresponds to the behavior of the previous version of the babel-plugin, before the appearance of the configuration, and corresponds to the current specification for the operator pipeline
in the repository. It is used more as a draft probe to reveal the advantages and disadvantages of other proposals, and is unlikely to be adopted without the drastic changes that are in the alternative proposals.
F # pipelines
Placeholders for F # pipelines are not needed at all. In the basic version, the switch functions close the need for placeholders, requiring less scribbling, and they are based on the syntax ES2015, which is familiar to everyone.
Currently (according to the specification of F # pipelines), the arrow functions must be wrapped in brackets:
let person = { score: 25 };
let newScore = person.score
|> double
|> (_ => add(7, _))
|> (_ => boundScore(0, 100, _));
Research is well underway to determine whether it is feasible to use pointer functions without parentheses, which here seem to be syntactically redundant.
As for asynchrony, in the F # pipelines it await
works as a unary function:
promise |> await
What unfolds in:
await promise
and therefore await
can be used in the middle of a long chain of asynchronous calls:
promise
|> await
|> (x => doubleSay(x, ', '))
|> capitalize
|> (x => x + '!')
|> (x => new User.Message(x))
|> (x => stream.write(x))
|> await
|> console.log;
Such special processing await
can potentially open up the possibility to use other unary operators in a similar way (for example, typeof
), but the original specification of F # pipelines does not contain them.
Smart pipelines
Smar pipelines bring the idea of placeholders to a logical conclusion, allowing partial applications and arbitrary expressions to be used in pipelines. The previous long string can be written like this:
promise
|> await #
|> doubleSay(#, ', ')
|> # || throw new TypeError()
|> capitalize
|> # + '!'
|> new User.Message(#)
|> await stream.write(#)
|> console.log;
The rules for using placeholders in the smart pipelines are quite simple. If a single identifier transmitted in step Pipeline, no additional token (placeholder) is not required, it is called a "minimalist style» ( "a bare style" ):
x |> a;
x |> f.b;
Unlike Hack, unary functions do not require a placeholder token.
For other expressions, the placeholder (called the "lexical topic token" - "thematic style token") is required, and the pipeline is considered to work within the "thematic style" - "topic style" . The absence of a placeholder token in this case causes an early SyntaxError error:
10 |> # + 1;
promise |> await #;
If there are any operators, brackets (including for the method call), quotes, or anything else besides the identifier and a point, then the placeholder token is required. This will help avoid shooting yourself in the leg and eliminate uncertainty.
Smart-Pipeline decide to support asynchronous problem in a more general way that allows the use of Pipeline in all possible expressions, not only await
, but also typeof
, yield
and any other statements.
Babel comes on the scene
Once all three sentences were concretized, we concluded that such discussions would not lead to the resolution of deep contradictions between the sentences. We decided that the best way is to collect feedback from developers using sentences in real code. Taking into account the role of Babel in the developer community, we decided to add all three options to the operator's plugin pipeline
.
Since the parsing for all three sentences is insignificant, but different, their support must first be added to @babel/parser
(which babylon
), and the parser should know which sentence needs to be supported now. Thus, the operator plugin pipeline
requires an option "proposal"
, both for configuring babylon for parsing, and for subsequent transformation.
We worked on this online, because we need to make all the changes that break backward compatibility before babel @ 7 ceases to be a beta. In the end, we would like to make one of the default pipelines for the plugin, in order to eliminate the need for a configuration option.
Given these limitations, we decided to add an option to the plugin configuration and make it mandatory, forcing users to decide which of the proposals they want to use in their project. As soon as a concrete proposal is selected as the canonical behavior of the operator, we mark the option "proposal"
as obsolete, and the canonical version will work by default. Support for canceled offers will work until the next major version.
Participate
If you want to participate in the discussion of the proposal, then all discussions are public and you can find them in the repository of the proposals of the pipeline operator . There is also a presentation from the meeting TC39 . After all, you can contact James DiGioia , JS Choi, or Daniel Ehrenberg on Twitter.
But more importantly, as soon as the work on the project pipeline
is completed, try it in your projects! We are also working on adding new features to the repl , so that you can check your code online. We need feedback, and using it in real code will help to gather it. Send tweets on @babeljs .
Only registered users can participate in the survey. Sign in , please.