Pipe Operator (| >) for JavaScript proposal to increase Pipe to js syntax, the combination of A Pipe Operator for JavaScript: Learn more about this proposal together with the introduction and Use Cases.

An overview of the

The Pipe syntax flattens function calls in order. The following function has three layers of nesting, but we need to read it from inside out because the order of calls is inside out:

const y = h(g(f(x)))
Copy the code

Pipe converts this to the normal order:

const y = x |> f(%) |> g(%) |> h(%)
Copy the code

Pipe syntax comes in two styles, one from Microsoft’s F# and one from Facebook’s Hack.

These two are introduced because the JS proposal first decides which style to “borrow” from. The js proposal ends up with a Hack style, so it’s best to look at both F# and Hack styles and compare the pros and cons to see why.

Hack Pipe grammar

Hack syntax is relatively redundant, passing results with % in Pipe:

'123.45' |> Number(%)
Copy the code

This % can be used anywhere, and 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
Copy the code

F # Pipe grammar

F# syntax is relatively compact and does not use additional symbols by default:

'123.45' |> Number
Copy the code

But when you need to declare parameters explicitly, it becomes more complicated to write to solve the problem of where the last Pipe result symbol came from:

2 |> $ => add2(1, $)
Copy the code

Await keyword – Hack

F# requires special syntax support in await yield, whereas hacks can naturally use js built-in keywords.

// Hack
value |> await %
// F#
value |> await
Copy the code

The F# code looks minimal, but it pays a high price – await is a keyword that exists only in the Pipe syntax, not the plain await keyword. If not processed as a keyword, the execution logic becomes await(value) instead of await value.

Deconstruct – F# excellent

F#’s cumbersome variable declarations make it handy when dealing with deconstructing scenarios:

// F#
value |> ({ a, b }) = > someFunction(a, b)
// Hack
value |> someFunction(%.a, %.b)
Copy the code

Hack is not without deconstruction means, but rather tedious. Either use the immediate call function expression IIFE:

value |> (({ a, b }) = > someFunction(a, b))(%)
Copy the code

Either use the do keyword:

value |> do { const { a, b } = %; someFunction(a, b) }
Copy the code

However, Hack is glorious though it fails, because the solutions all use the native syntax provided by js, so it reflects a stronger ecological affinity with js. The elegant solution of F# is all due to the self-created syntax, which is sweet but splits the js ecology. This was one of the big reasons why the F# like proposal was abandoned.

Potential improvement options

Although the choice of Hack style, but F# and Hack have their own advantages and disadvantages, so listed several optimization schemes.

Reduce F# parameter complexity with Partial Application Syntax proposal

One of the reasons F# has been criticized is that it’s not as easy to upload as Hack:

// Hack
2 |> add2(1, %)
// F#
2 |> $ => add2(1, $)
Copy the code

However, if you use Partial Application Syntax in stage1, you can solve the problem very well.

This is going to be a little interlude. Js has no native support for Corrification, but the Partial Application Syntax proposal addresses this issue with the following Syntax:

const add = (x, y) = > x + y;
const addOne = add~(1, ?);
addOne(2); / / 3
Copy the code

It is using fn ~ (? , arg), to corrize any function. This feature is perfect for solving the complex problem of F# passing parameters, since every Pipe of F# requires a function, we can call the place where the parameter is passed? The return value is still a function, which perfectly conforms to the syntax of F# :

// F#
2 |> add~(1,?)Copy the code

The above example is broken down as follows:

const addOne = add~(1,?)2 |> addOne
Copy the code

Nice idea, but Partial Application Syntax needs to land first.

Fusing F# with Hack syntax

To use F# in the simple case, use Hack syntax when passing the % argument, and mix the two together:

const resultArray = inputArray
  |> filter(%, str= > str.length >= 0) // Hack
  |> map(%, str= > '['+str+'] ') // Hack
  |> console.log // F#
Copy the code

But the proposal was scrapped.

Create a new operator

If use | > Hack grammar, represented by | > > said f # grammar?

const resultArray = inputArray
  |> filter(%, str= > str.length >= 0) // Hack
  |> map(%, str= > '['+str+'] ') // Hack
  |>> console.log // F#
Copy the code

It looks great, but this feature isn’t even proposed yet.

How do I emulate a Pipe with an existing syntax

Even without Pipe Operator (| >) for JavaScript proposal, also can use js existing syntax to simulate Pipe effect, here are several kinds of schemes.

Function.pipe()

The pipe method is constructed using a custom function that is similar to F# :

const resultSet = Function.pipe(
  inputSet,
  $ => filter($, x= > x >= 0)
  $ => map($, x= > x * 2)
  $ => new Set($))Copy the code

The disadvantage is that there is no await support and there are extra function calls.

Use intermediate variables

Break the Pipe process apart and write it step by step:

const filtered = filter(inputSet, x= > x >= 0)
const mapped = map(filtered, x= > x * 2)
const resultSet = new Set(mapped)
Copy the code

No big problem, just redundant, three lines of problems that could have been solved in one line, and three intermediate variables were declared.

Reuse the variable

Modify the intermediate variable to be reusable:

let $ = inputSet
$ = filter($, x= > x >= 0)
$ = map($, x= > x * 2)
const resultSet = new Set($)
Copy the code

There may be variable contamination in doing so, which can be resolved using IIFE.

Intensive reading

The semantic value of Pipe Operator is so obvious that it can even change the way you think about programming, and is important when processing data serially, that command line scenarios are common, such as:

cat "somefile.txt" | echo
Copy the code

Because the command line is a typical I/O scenario, and most of it is single input, single output.

This feature is also needed in common code scenarios, especially when dealing with data. Most abstract-thinking code performs various types of pipe abstractions, such as:

const newValue = pipe(
  value,
  doSomething1,
  doSomething2,
  doSomething3
)
Copy the code

If the Pipe Operator (| >) for proposed by JavaScript, we don’t need any library implementation Pipe action, can be directly written:

const newValue = value |> doSomething1(%) |> doSomething2(%) |> doSomething3(%)
Copy the code

This is equivalent to:

const newValue = doSomething3(doSomething2(doSomething1(value)))
Copy the code

Obviously, using the PIPE feature to write the process flow is more intuitive, and the execution logic is consistent with the reading logic.

Implementing the PIPE function

Even without Pipe Operator (| >) for JavaScript proposal, we can also realize Pipe function:

const pipe = (. args) = > args.reduce((acc, el) = > el(acc))
Copy the code

However, it is impossible to implement Hack parameter style, at most implement F# parameter style.

Js implementation pipe syntax considerations

From the record of proposals, F# failed for three reasons:

  • Memory performance problems.
  • awaitSpecial syntax.
  • Split js ecology.

Fragmentation of the js ecosystem means that due to the particularity of F# syntax, if there are too many libraries implementing functions according to its syntax, it may not be reused by non-pipe syntax scenarios.

Some members even object to Tacit programming and coriation proposal Partial Application Syntax, which make the programming style supported by JS greatly different from the present one.

It seems that the programming style at the top of the contemptuous chain is not a matter of whether JS is supported or not, but a matter of whether you want it or not.

The drawbacks of pipe syntax

Here is the plain setState syntax:

setState(state= > ({
  ...state,
  value: 123
}))
Copy the code

If you change it to immer, write it as follows:

setState(produce(draft= > draft.value = 123))
Copy the code

Thanks to the automatic derivation of ts type, inner Produce already knows that value is a string type. In this case, an error will be reported if the input string is in setState in another context, and the type will change with the context.

But in PIPE mode:

produce(draft= > draft.value = 123) |> setState
Copy the code

Because the first consideration is how to modify the data, it is not yet known what the pipe process will be, so the type of draft cannot be determined. So pipe syntax is only suitable for fixed types of data processing flows.

conclusion

Each function is a different pipe. The next pipe processes the data from the previous pipe and outputs the results to the next pipe as input.

The appropriate number and volume of pipes determine whether a production line is efficient or not. Too many pipe types will make the assembly line scattered and disorderly, while too few pipes will make the assembly line bulky and difficult to expand, which is the biggest test in work.

The address is: Pipe Operator for JavaScript · Issue #395 · dt-fe/weekly

If you’d like to participate in the discussion, pleaseClick here to, with a new theme every week, released on weekends or Mondays. Front end Intensive Reading – Helps you filter the right content.

Copyright Notice: Freely reproduced – Non-commercial – Non-derivative – Remain signed (Creative Commons 3.0 License)