Pipe Operator (|>) for JavaScript proposal adds a Pipe syntax to js. This time, we combine the A pipe operator for JavaScript: introduction and use cases article to learn more about this proposal.
Overview
The Pipe syntax flattens function calls in sequence. The following function has three levels of nesting, but we need to read it from the inside out when we interpret it, because the calling order is from the inside out:
const y = h(g(f(x)))
Pipe can convert this to normal order:
const y = x |> f(%) |> g(%) |> h(%)
Pipe syntax comes in two flavors, from Microsoft's F# ) and Facebook's Hack ).
These two are introduced because the js proposal first decides which style to "borrow" from. The js proposal finally adopts the Hack style, so we'd better understand both the F# and Hack styles, and compare their pros and cons to know why.
Hack Pipe Syntax
Hack syntax is relatively redundant, use %
to pass the result when Pipe:
'123.45' |> Number(%)
This %
can be used anywhere, basically native js syntax supports:
value |> someFunction(1, %, 3) // function calls
value |> %.someMethod() // method call
value |> % + 1 // operator
value |> [%, 'b', 'c'] // Array literal
value |> {someProp: %} // object literal
value |> await % // awaiting a Promise
value |> (yield %) // yielding a generator value
F# Pipe syntax
F# syntax is relatively compact and does not use extra symbols by default:
'123.45' |> Number
But when you need to explicitly declare the parameters, in order to solve the problem of where the last Pipe result symbol comes from, it is more complicated to write:
2 |> $ => add2(1, $)
await keyword - Hack Advantage
F# needs special syntax support for await
yield
, and Hack can naturally use js built-in keywords.
// Hack
value |> await %
// F#
value |> await
F# code looks lean, but it actually comes at a high price - await
is a keyword that exists only in Pipe syntax, not the normal await
keyword. If not handled as a keyword, the execution logic becomes await(value)
instead of await value
.
Destructuring - F# Excellent
It is because of the cumbersome variable declaration of F# that it is handy when dealing with destructuring scenarios:
// F#
value |> ({ a, b }) => someFunction(a, b)
// Hack
value |> someFunction(%.a, %.b)
Hack is not without deconstruction methods, but it is more cumbersome. Either use an immediate call function expression IIFE:
value |> (({ a, b }) => someFunction(a, b))(%)
Either use the do
keyword:
value |> do { const { a, b } = %; someFunction(a, b) }
However, although Hack is defeated, because the solutions all use the grammar provided by js natively, it reflects a stronger ecological affinity with js, and the reason why F# can be solved elegantly is all due to its own grammar. Although these syntaxes are sweet, they separate the js ecology, which is one of the important reasons why the F# like proposal was abandoned.
Potential Improvements
Although the Hack style is chosen, F# and Hack have their own advantages and disadvantages, so a few optimization schemes are listed.
Using the Partial Application Syntax proposal to reduce the complexity of F# parameter passing
One of the reasons F# has been criticized is that passing parameters is not as easy as hacking:
// Hack
2 |> add2(1, %)
// F#
2 |> $ => add2(1, $)
But if you use the proposal Partial Application Syntax in stage1, the problem can be solved very well.
Here's a little interlude. js has no native support for currying, but the Partial Application Syntax proposal solves this problem with the following syntax:
const add = (x, y) => x + y;
const addOne = add~(1, ?);
addOne(2); // 3
That is, use the syntax of fn~(?, arg)
to curry any function. This feature is perfect for solving the complex problem of passing parameters in F#, because every Pipe in F# requires a function. We can record the place to pass parameters as ?
, so the return value is still a function, which perfectly conforms to the syntax of F#:
// F#
2 |> add~(1, ?)
The above example is disassembled to see:
const addOne = add~(1, ?)
2 |> addOne
Great idea, but Partial Application Syntax to be implemented first.
Fusion of F# and Hack syntax
To use F# in a simple case, you need to use Hack syntax when passing parameters using %
. The two are mixed together to write:
const resultArray = inputArray
|> filter(%, str => str.length >= 0) // Hack
|> map(%, str => '['+str+']') // Hack
|> console.log // F#
However, this proposal was abandoned.
create a new operator
What if you use |>
for Hack syntax and |>>
for F# syntax?
const resultArray = inputArray
|> filter(%, str => str.length >= 0) // Hack
|> map(%, str => '['+str+']') // Hack
|>> console.log // F#
It also looks nice, but this feature has not even been proposed yet.
How to mock Pipe with existing syntax
Even if there is no Pipe Operator (|>) for JavaScript proposal, the existing js syntax can be used to simulate the Pipe effect. The following are several solutions.
Function.pipe()
Construct the pipe method with a custom function, the syntax is similar to F#:
const resultSet = Function.pipe(
inputSet,
$ => filter($, x => x >= 0)
$ => map($, x => x * 2)
$ => new Set($)
)
The disadvantage is that await
is not supported, and there are extra function calls.
use intermediate variables
To put it bluntly, it is to disassemble the Pipe process and write step by step:
const filtered = filter(inputSet, x => x >= 0)
const mapped = map(filtered, x => x * 2)
const resultSet = new Set(mapped)
There is no big problem, but it is redundant. The problem that could be solved in one line has become three lines, and three intermediate variables are also declared.
Reuse variables
Modify it to make intermediate variables reused:
let $ = inputSet
$ = filter($, x => x >= 0)
$ = map($, x => x * 2)
const resultSet = new Set($)
Doing so may have variable pollution, which can be resolved with IIFE.
intensive reading
The semantic value of Pipe Operator is very obvious, and it can even change the way of thinking of programming. It is very important when processing data serially, so command-line scenarios are very common, such as:
cat "somefile.txt" | echo
Because the command line is a typical input and output scene, and most of them are single input and single output.
This feature is also required in ordinary code scenarios, especially when processing data. Most of the code with abstract thinking has implemented various types of pipeline abstraction, such as:
const newValue = pipe(
value,
doSomething1,
doSomething2,
doSomething3
)
If Pipe Operator (|>) for JavaScript proposal is passed, we don't need any library to implement the pipe action, we can directly write:
const newValue = value |> doSomething1(%) |> doSomething2(%) |> doSomething3(%)
This is equivalent to:
const newValue = doSomething3(doSomething2(doSomething1(value)))
Obviously, it is more intuitive to use the pipe feature to write the processing flow, and the execution logic is consistent with the reading logic.
Implement the pipe function
Even without Pipe Operator (|>) for JavaScript proposal, we can implement the pipe function in one line:
const pipe = (...args) => args.reduce((acc, el) => el(acc))
But it is impossible to implement Hack parameter style, at most F# parameter style.
js implementation of pipe syntax considerations
From the proposal records, F# fails for three reasons:
- Memory performance issues.
await
Special syntax.- Split js ecology.
Among them, splitting the js ecology means that due to the particularity of F# syntax, if there are too many libraries to implement functions according to their syntax, it may not be reused by non-Pipe syntax scenarios.
There are even some members who oppose Tacit programming , and the currying proposal Partial Application Syntax , which will make the programming style supported by js too different from the current one.
It seems that the programming style at the top of the contempt chain is not a question of whether or not js is supported, but a question of whether you want it or not.
Disadvantages of pipe syntax
Here is the normal setState
syntax:
setState(state => ({
...state,
value: 123
}))
If it is changed to immer
written as follows:
setState(produce(draft => draft.value = 123))
Thanks to the automatic derivation of the ts type, it is already known in the inner layer produce
that value
is a string type. At this time, if the input string is an error, if it is in another context setState
, the type will also change with the context. and change.
But if written in pipe mode:
produce(draft => draft.value = 123) |> setState
Because the first consideration is how to modify the data, at this time it is not known what the subsequent pipeline process is, so the type of draft
cannot be determined. So the pipe syntax is only suitable for a fixed type of data processing flow.
Summarize
Pipe is literally translated as a pipeline. The potential meaning is "data is processed like a pipeline". It can also be understood as a different pipeline for each function. Obviously, the next pipeline needs to process the data of the previous pipeline and output the result to the next one. pipe as input.
The appropriate number and volume of pipelines determine whether a production line is efficient. Too many types of pipelines will make the pipeline scattered and cluttered. Too few pipelines will make the pipeline bulky and difficult to expand. This is the biggest test in work.
The discussion address is: Intensive Reading "pipe operator for JavaScript" Issue #395 dt-fe/weekly
If you want to participate in the discussion, please click here , there are new topics every week, released on weekends or Mondays. Front-end intensive reading - help you filter reliable content.
Pay attention to front-end intensive reading WeChat public number
<img width=200 src="https://img.alicdn.com/tfs/TB165W0MCzqK1RjSZFLXXcn2XXa-258-258.jpg">
Copyright notice: Free to reprint - non-commercial - non-derivative - keep attribution ( Creative Commons 3.0 license )
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。