The "Pipeline" Operator - Making multiple function calls look great

# Gilbert B Garza (3 years ago)

Hello, I'm a JavaScript programmer and instructor who loves functional programming and writing concise, readable code. I think in general JavaScript supports programming in a functional style quite well. However, there is one small missing piece that I miss from other FP languages: the simple-yet-useful pipeline operator.

Similar to F#, Elixir, Elm, and other FP languages, the pipeline operator |> helps make multiple function invocations more readable. Basically, sqrt(64) is equivalent to 64 |> sqrt. For example, given the following

functions:

function doubleSay (str) { return str + ", " + str; }
function capitalize (str) { return str[0].toUpperCase() + str.substring(1);
}
function exclaim (str) { return str + '!'; }

you could use the pipeline operator to expand your invocations for readability:

// old way:
// var result = exclaim(capitalize(doubleSay("hello")));

// new way:
var result = "hello"
  |> doubleSay
  |> capitalize
  |> exclaim;

// or, if you like one-liners:
var result = "hello" |> doubleSay |> capitalize |> exclaim

result //=> "Hello, hello!"

You can see a few more examples, including an advanced example with Promises, here: mindeavor/ES7-pipeline-operator

I'm inclined to think this feature is small and straight-forward to implement. Other than the operator, there are no new semantics. The syntax transformation is simple, and all existing code would remain unaffected.

Although small, this feature increases the expressiveness of JavaScript significantly by opening more API design possibilities. You can see this in the link I included above.

Thanks for reading. Any thoughts or comments?

# Alexander Fritze (3 years ago)

I agree 100% that this would make a great addition to JS!

We've had the pipeline operator in Stratified JS for a while - see conductance.io/reference#sjs:%23language/syntax::double-dot - , and in our experience it makes a lot of code much more readable.

We call it '..' (the 'doubledot operator') instead of '|>' - IMO this

feels more aligned with existing JS syntax and less jarring than '|>'.

# Felipe Nascimento de Moura (3 years ago)

I find this interesting, indeed.

Although, I believe the ( ) are like "representations OF a function call". I felt like if there was something missing there! What about something like this, then?

// exclaim(capitalize(doubleSay("hello"))); doubleSay("hello") |> capitalize |> exclaim;

For me, this feels like "calling doubleSay", then passing its results for capitalize, and its result to exclaim.

Some interesting implementations coupd be done with async await, I believe!

# Kevin Smith (3 years ago)

Have you seen zenparsing/es-function-bind ? It's a function bind-syntax proposal which covers some of the same use cases (although by binding the this parameter instead of passing the first arg). We've also explored some alternatives more closely aligned with your proposal. Check out the discussion here for the latest state: zenparsing/es-function-bind#26 and feel free to comment!

# Gilbert B Garza (3 years ago)

Kevin: Ah yes, I have studied function-bind, but I have two issues with it:

  • I don't like the requirement to use the keyword this to compose functions. JS already has many features to support the keyword this: prototypes, method invocations, function binding, arrow functions, and probably others. I prefer a feature that assists the other side of the spectrum.

  • The fact that there are new semantics to what looks like a normal function call (e.g. ->map(...)) doesn't set well with me. You could argue

that it's something to get used to. Even in that case, I would expect the first argument I give to map to stay the first argument.

With the pipeline operator, partial application is left to the developer. They can choose to use arrow functions, or to curry their functions. I think this is the best option since it keeps things simple (no new semantics), and remains readable – see the "Function with Multiple Arguments" section in the GitHub link mindeavor/ES7

# Gilbert B Garza (3 years ago)

Although, I believe the ( ) are like "representations OF a function call". I felt like if there was something missing there! What about something like this, then?

// exclaim(capitalize(doubleSay("hello")));

doubleSay("hello") |> capitalize |> exclaim;

Felipe: That is perfectly valid syntax under the current proposal, and you are free to choose to write your code in that way :)

# Kevin Smith (3 years ago)
  • I don't like the requirement to use the keyword this to compose functions. JS already has many features to support the keyword this: prototypes, method invocations, function binding, arrow functions, and probably others. I prefer a feature that assists the other side of the spectrum.

Yep - a well documented critique. It depends on your point of view, I think. If you view these things as "extension methods", then using this makes more sense.

  • The fact that there are new semantics to what looks like a normal

function call (e.g. ->map(...)) doesn't set well with me. You could argue that it's something to get used to. Even in that case, I would expect the first argument I give to map to stay the first argument.

This is a reasonable objection, I think.

With the pipeline operator, partial application is left to the developer.

They can choose to use arrow functions, or to curry their functions. I think this is the best option since it keeps things simple (no new semantics), and remains readable – see the "Function with Multiple Arguments" section in the GitHub link mindeavor/ES7-pipeline-operator

I agree that your proposal wins points for simplicity (both semantic and syntactic), but having to create an arrow function to pass more than one argument feels a bit awkward and seems to defeat some of the readability benefit.

# Isiah Meadows (3 years ago)

Inline

On Tue, Nov 10, 2015 at 12:52 PM, Kevin Smith <zenparsing at gmail.com> wrote:

  • I don't like the requirement to use the keyword this to compose functions. JS already has many features to support the keyword this: prototypes, method invocations, function binding, arrow functions, and probably others. I prefer a feature that assists the other side of the spectrum.

Yep - a well documented critique. It depends on your point of view, I think. If you view these things as "extension methods", then using this makes more sense.

  • The fact that there are new semantics to what looks like a normal function call (e.g. ->map(...)) doesn't set well with me. You could argue that it's something to get used to. Even in that case, I would expect the first argument I give to map to stay the first argument.

This is a reasonable objection, I think.

Not to mention it's still a point of contention: zenparsing/es-function-bind#26 (from here, down)

With the pipeline operator, partial application is left to the developer. They can choose to use arrow functions, or to curry their functions. I think this is the best option since it keeps things simple (no new semantics), and remains readable – see the "Function with Multiple Arguments" section in the GitHub link mindeavor/ES7-pipeline-operator

I agree that your proposal wins points for simplicity (both semantic and syntactic), but having to create an arrow function to pass more than one argument feels a bit awkward and seems to defeat some of the readability benefit.

Not to mention it would be pretty slow. That's going to be creating a closure each call. Engines still struggle with making closures fast. It's non-trivial at runtime to create a closure that's performant.

# Gilbert B Garza (3 years ago)

I agree that your proposal wins points for simplicity (both semantic and syntactic), but having to create an arrow function to pass more than one argument feels a bit awkward and seems to defeat some of the readability benefit.

I find it to be pleasant :) It gives opportunity to semantically name your arrow function parameter, and it also follows the consistency of "the value on the left gets fed to the function on the right".

But yes, I do agree having partial application somehow built-in would be nice.

Not to mention it would be pretty slow. That's going to be creating a closure

each call.

Normally yes, but since the pipeline operator is a pure function, I think it's possible for the compiler to optimize away the intermediate arrow functions. I mention this in the "Functions with Multiple Arguments" section mindeavor/ES7-pipeline-operator#functions

# Nelo Mitranim (3 years ago)

Seems pointless. Functions can be trivially composed to form a “pipeline” without introducing new syntax: lodash.com/docs#flow

# Gilbert B Garza (3 years ago)

Nelo, that is function composition. While it's a very useful concept, it isn't quite the same thing; you have to first create the composed function before using it. What I'm proposing is an operator that allows ad-hoc chained invocations, with a primary goal of having the input read first.

# Isiah Meadows (3 years ago)

Not with your semantics. It has to generate a closure each time, because of the possibility it can't be used elsewhere. That's impossible to know ahead of time if the variable is ever used outside of its closure or as a value. In the case below, it should only log thrice. Otherwise, it's unexpected behavior.

function add(x) {
  console.log("Hit!");
  return y => x + y;
}

let inc = add(1);

1 |> inc |> inc |> add(2) |> add(3);

// Hit!
// Hit!
// Hit!

On Tue, Nov 10, 2015, 15:08 Gilbert B Garza <gilbertbgarza at gmail.com> wrote:

Normally yes, but since the pipeline operator is a pure function, I think it's possible for the compiler to optimize away the intermediate arrow functions. I mention this in the "Functions with Multiple Arguments" section https mindeavor/ES7-pipeline-operator#functions-with-multiple-arguments :// mindeavor/ES7-pipeline-operator#functions-with-multiple-arguments

github.com, mindeavor/ES7-pipeline-operator#functions-with-multiple-arguments / mindeavor/ES7-pipeline-operator#functions-with-multiple-arguments

mindeavor mindeavor/ES7-pipeline-operator#functions-with-multiple-arguments / mindeavor/ES7-pipeline-operator#functions-with-multiple-arguments

ES7-pipeline-operator#functions mindeavor/ES7-pipeline-operator#functions-with-multiple-arguments -with-multiple-arguments mindeavor/ES7-pipeline-operator#functions-with-multiple-arguments

On Tue, Nov 10, 2015 at 12:52 PM, Isiah Meadows <isiahmeadows at gmail.com>

wrote:

Inline

On Tue, Nov 10, 2015 at 12:52 PM, Kevin Smith <zenparsing at gmail.com> wrote:

  • I don't like the requirement to use the keyword this to compose functions. JS already has many features to support the keyword this: prototypes, method invocations, function binding, arrow functions, and probably others. I prefer a feature that assists the other side of the spectrum.

Yep - a well documented critique. It depends on your point of view, I think. If you view these things as "extension methods", then using this makes more sense.

  • The fact that there are new semantics to what looks like a normal function call (e.g. ->map(...)) doesn't set well with me. You could

argue

that it's something to get used to. Even in that case, I would expect the first argument I give to map to stay the first argument.

This is a reasonable objection, I think.

Not to mention it's still a point of contention: https zenparsing/es-function-bind#26 :// zenparsing/es-function-bind#26

github.com, zenparsing/es-function-bind#26 / zenparsing/es-function-bind#26

zenparsing zenparsing/es-function-bind#26 / zenparsing/es-function-bind#26

es-function-bind zenparsing/es-function-bind#26 /issues/26#issuecomment-154130932 zenparsing/es-function-bind#26 (from here, down)

With the pipeline operator, partial application is left to the developer. They can choose to use arrow functions, or to curry their functions. I

think

this is the best option since it keeps things simple (no new semantics),

and

remains readable – see the "Function with Multiple Arguments" section in

the

GitHub link https mindeavor/ES7-pipeline-operator:// mindeavor/ES7-pipeline-operatorgithub.com, mindeavor/ES7-pipeline-operator/ mindeavor/ES7-pipeline-operatormindeavor mindeavor/ES7-pipeline-operator/ mindeavor/ES7-pipeline-operatorES7-pipeline-operator mindeavor/ES7-pipeline-operator

I agree that your proposal wins points for simplicity (both semantic and syntactic), but having to create an arrow function to pass more than one argument feels a bit awkward and seems to defeat some of the readability benefit.

Not to mention it would be pretty slow. That's going to be creating a closure each call. Engines still struggle with making closures fast. It's non-trivial at runtime to create a closure that's performant.


es-discuss mailing list es-discuss at mozilla.org https mail.mozilla.org/listinfo/es-discuss:// mail.mozilla.org/listinfo/es-discussmail.mozilla.org, mail.mozilla.org/listinfo/es-discuss/ mail.mozilla.org/listinfo/es-discusslistinfo mail.mozilla.org/listinfo/es-discuss/ mail.mozilla.org/listinfo/es-discusses-discuss mail.mozilla.org/listinfo/es-discuss

-- Isiah Meadows

# Gilbert B Garza (3 years ago)

Ah, sorry for being unclear. You're right, in the case of manual currying, the closure can not and should not be optimized away.

I was talking about the case where you have arrow function literals. For example:

var bounded = 750
    |> s => Math.max(100, s)
    |> s => Math.min(0, s);

I imagine if the compiler sees arrow functions used in this specific manner, it could automatically optimize to the following:

var bounded = Math.min( 0, Math.max(100, 750) )

Semantically, they are equivalent; no closures nor scopes were effectively used in the intermediary arrow functions.

# Gilbert B Garza (3 years ago)

Status update: The JS community has shown lots of excitement for the idea of this proposal, but the syntax is still being debated. I outlined two alternatives in this GitHub issue, one of which I will post here since it is my current favorite:

Placeholder Arguments

The alternative is to have a new syntax for "placeholder arguments" – basically, slots waiting to be filled by the next function call. For example:

//
// Placeholder style
//
run("hello") |> withThis(10, #);
// is equivalent to
withThis( 10, run("hello") );

//
// More complicated example
//
run("hello") |> withThis(10, #) |> andThis(#, 20);
// is equivalent to
andThis( withThis( 10, run("hello") ), 20 );

Pros / Cons

  • [pro] Very explicit (no surprises)
  • [pro] Less function calls
  • [pro] Compatible with even more of the JavaScript ecosystem
  • [con] Requires more new syntax
  • [con] Usage of the hash operator (#) would probably be hard to define outside coupled use with pipeline operator (this problem could be avoided by simply making it illegal)
# Caitlin Potter (3 years ago)

These examples only make this language extension look like a confusing and horrible thing. How is this an improvement, in any way?

# Gilbert B Garza (3 years ago)

Confusing and horrible are subjective, but I do agree that it's not as nice as the original proposal. Although I prefer the original, it's also important to discuss objections that people bring up, as well as the alternatives that might solve said objections :)

Gilbert

# Alexander Jones (3 years ago)

Why not just have a rule that the pipeline operator consumes unary functions, and reuse existing bind idioms, or a future bind syntax? That seems like a proper separation of concerns to me.

As with those future bind syntax proposals, the idea of just sticking a token somewhere within an expression to turn it into a lambda is not ideal due to the various, obvious ambiguities that can arise. c.f. arrow functions where the argument is given an explicit name.

# Caitlin Potter (3 years ago)

On Dec 6, 2015, at 4:51 PM, Gilbert B Garza <gilbertbgarza at gmail.com> wrote:

Confusing and horrible are subjective, but I do agree that it's not as nice as the original proposal. Although I prefer the original, it's also important to discuss objections that people bring up, as well as the alternatives that might solve said objections :)

Even with the original solution, it just creates a new (and less obvious) way of writing something that is already possible in the language, and easier to understand. There is a narrow scope of times where it even makes sense to use the original (or enhanced) version of the proposal, and this requires authors to be vigilante about knowing when to pick one form over the other. It also requires code reviewers to be vigilante in knowing when to say “know, this is a bad idea, stop”.

It doesn’t look like there is any redeeming quality about this change, it doesn’t introduce convenience, and does introduce new pain points for authors. Anything involving “placeholders arguments” is especially bad because it introduces an extra thing to reason about, but the issue with introducing a new syntax to pick and be aware of for limited gains is also problematic, growing the language and increasing complexity for a less than substantial reason.

The bind operator may at least have the benefit of providing some out of the box support for classic FP style, so it’s got that going for it (but really, the language doesn’t need three distinct syntaxes/idioms for this pattern, the complexity budget is already running thin)

# Gilbert B Garza (3 years ago)

It doesn’t look like there is any redeeming quality about this change, it

doesn’t introduce convenience

I think you may have missed the GitHub repo [1]; it outlines benefits and convenience about the change. Also see this user's comment [2] on its significance.

[1] mindeavor/es-pipeline-operator [2] news.ycombinator.com/item?id=10686596

I argue that the syntax transformation is so simple that it would not introduce any pain points for JavaScript authors. It is arguably more intuitive than its alternative. JS programmers who are partial to FP certainly agree [3][4].

[3] twitter.com/markdalgleish/status/673581814028492800 [4] www.reddit.com/r/javascript/comments/3vox7x/es7_proposal_the_pipeline_operator

Anything involving “placeholders arguments” is especially bad because it

introduces an extra thing to reason about

I agree; I still prefer the original proposal.

The bind operator may at least have the benefit of providing some out of

the box support for classic FP style

It does not, unfortunately. FP devs avoid the keyword this, but the bind operator requires its use. If I'm not mistaken, the "chaining" part of the bind operator is only a minor point, and isn't the main purpose of the proposal.

# Rick Waldron (3 years ago)

Has anyone tried writing grammar for this? The "|>" token requires a a

lookahead to disambiguate the bit wise OR operator |

# Felipe Nascimento de Moura (3 years ago)

What about a backslash instead? Like escaping a function call, such as:

foo("some string")\bar\baz;

Or

some ${data} \ foo \ bar \ console.log;

# Gilbert B Garza (3 years ago)

Rick: I would imagine some sort of lookahead is already in the parser, considering JavaScript supports both bitwise OR | and boolean OR || – unless this is accomplished some other way?

Gilbert

# Alexander Jones (3 years ago)

I wonder whether we've reach that point in the language where trying to reuse the same old tokens over and over again in new combinations is getting untenable. It's a long road to APL...

# Rick Waldron (3 years ago)

Correcting myself: this would be a Punctuator

# Gilbert B Garza (3 years ago)

Update: Another great use case has been discovered for the pipeline op – usage with async / await:

// Assume fs.readFile is an `async` function
async function runTask () {
  './index.txt'
    |> await fs.readFile
    |> file => file
       .split('\n')
       .map(fs.readFile)
    |> await Promise.all
    |> all => all.join("\n")
    |> console.log
}
# Kevin Smith (3 years ago)
// Assume fs.readFile is an `async` function
async function runTask () {
  './index.txt'
    |> await fs.readFile
    |> file => file
       .split('\n')
       .map(fs.readFile)
    |> await Promise.all
    |> all => all.join("\n")
    |> console.log
}

This doesn't work unless you special case the semantics of await expressions. With the current semantics, await fs.readFile will just await fs.readFile not the result of applying it.

# Gilbert B Garza (3 years ago)

Ah yes, you are correct, it would need to be a special case as I wrote it. This version should work instead:

// Assume fs.readFile is an `async` function
async function runTask () {
  fs.readFile('./index.txt')
    |> await
    |> file => file
       .split('\n')
       .map(fs.readFile)
    |> Promise.all
    |> await
    |> all => all.join("\n")
    |> console.log
}
# Isiah Meadows (3 years ago)

Question: does x |> f(y) desugar to f(x, y), f(y, x), or f(y)(x)?

# Gilbert B Garza (3 years ago)

x |> f(y) desugars to f(y)(x)

# Isiah Meadows (3 years ago)

Is there a reason why it couldn't desugar to f(x, y) or f(y, x)? I think it would be more performant with mostly the same benefits, and it would work better with existing libraries, as a compliment to the function bind proposal.

# Gilbert B Garza (3 years ago)

I also thought as much, but after a long discussion we determined the current proposal is better – it's simple and doesn't change any semantics of invocation. Also, arrow functions are a fine solution to working with functions that take multiple arguments, esp. since they can be optimized out by the compiler (as I do in my babel plugin).

# Alican Çubukçuoğlu (3 years ago)

This is a terrible example. It looks a lot better with what's already available:

async function runTask () {
  const file = await fs.readFile('./index.txt');

  const all = await* file
    .split('\n')
    .map(fs.readFile);

  console.log(all.join('\n'));
}

Also how do you explain this feature to a newbie? This operator is supposed to call the function at the RHS with the value at the LHS and now it also has a special case when there is await at the RHS. How more complicated will this proposal get for the sake of making it look useful?

This proposal's aim should basically be turning this:

myFunc(myVar);

into this:

myVar |> myFunc;

If it is not useful by just doing that then it should simply be rejected.


About the usefulness of this proposal, this one conflicts with the function bind syntax and assuming FBS is all rainbows and unicorns and everyone should use it, then this proposal makes no sense since the original example should be written like this (and it's already good enough):

function doubleSay () {
  return this + ', ' + this;
}
function capitalize () {
  return this[0].toUpperCase() + this.substring(1);
}
function exclaim () {
  return this + '!';
}

const result = 'hello'
  ::doubleSay()
  ::capitalize()
  ::exclaim(); // "Hello, hello!"

It's also weird that how the RHS of PO become weird when the function takes extra parameters. You can see how the code becomes polluted:

// Have fbFunc) with 0 parameters
citizen::fbsFunc();

// Add a parameter to fbsFunc()
citizen::fbsFunc(param);

// Have opFunc() with 0 parameters
citizen |> opFunc;

// Add a parameter to opFunc()
citizen |> _ => opFunc(_, param);

So we would be lying if we said the PO syntax is param |> func.

The reality is:

// FBS syntax:
param1::fbsFunc(param2);

// PO syntax:
param1 |> _ => opFunc(_, param2);

This is no good.

# Kevin Smith (3 years ago)

I can confirm that function bind syntax is all rainbows and unicorns.

: p

# Gilbert B Garza (3 years ago)

Alican, I am one to find brainstorming useful. I appreciate your feedback (you make good points about await), but your tone might be taken as not constructive.

I think you may be misunderstanding how the PO works. Specifically your example here:

// Have opFunc() with 0 parameters
citizen |> opFunc;

That would be equivalent to opFunc(citizen), which has one parameter.

To be clear, I don't think the PO conflicts with the bind operator. The undeniable fact is that there are going to be functions that receive their subject as a parameter, and – far fewer, I might add – functions that receive their subject as the keyword this. Both are valid and useful approaches, and I don't see them as conflicting with one another.

As mentioned in many other discussions, dealing with functions that take multiple parameters is actually a separate issue – partial application. Even so, functional programmers find good ways around the lack of built-in partial application in JavaScript:

let add = (x, y) => x + y;

let addCurried = x => y => x + y;

// "papp" stands for "partial application"
Function.prototype.papp = ...;

var result = 10
  |> add.papp(20)
  |> addCurried(30)
;
result //=> 60

In fact, if papp were built into the language, it would be simple for compilers to optimize away an extra function call.

But I digress. My main point is that support for partial application is a separate issue. The fact that the pipeline operator works without it is a good thing (at least, as I see it).

# Alican Çubukçuoğlu (3 years ago)

I think you may be misunderstanding how the PO works. Specifically your

example here:

Just a typo. Let me list my concerns:

  • Supporting await makes things unnecessarily complicated.
  • Calling functions with more than one parameter is weird.
  • Looks like use cases are already covered by FBS.
# Felipe Nascimento de Moura (3 years ago)

my 50 cents.

I think it is interesting to be added I think this should not really concern to await implementations (would, indeed, get weirder and more complicated)! I don't think this has anything to do with bind! No "this" is touched with that! I don't think it has anything to do with chaining either.

How I see it:

// consider:
function getUserName () { /* ... */ }
function capitalize (str) { /* ... */ }
function trim (str) { /* ... */ }
function splitBySpaces (str) { /* ... */ }
// Using PO:
console.log( getUserName() |> capitalize |> trim |> splitBySpaces );
// NOT using PO:
let name = getUserName();
name = capitalize(name);
name = trim(name);
console.log( splitBySpaces(name) );

// OR

console.log( splitBySpaces( trim( capitalize( getUserName() ) ) ) );

This is not related with "this", nor with chaining(once it can be used with any function, even when, as in this case, returning strings). This is not like "then" either, once it is not temporal, but way easier to read and understand!

Therefore...I don't think this should be consider to work with await. I don't they belong to the same realm!

Although, I believe this would work too:

// imagine getUserName and trim are async

console.log(await getUserName() |> capitalize |> await trim |>

splitBySpaces );

Thanks for your time :)

# Alican Çubukçuoğlu (3 years ago)

So these are your example utility functions:

function capitalize (str) { /* ... */ }
function trim (str) { /* ... */ }
function splitBySpaces (str) { /* ... */ }

What makes you think that utility functions should be coded like that? Take these for an example:

// Using "this"
function trim(characters) {
  // this...
  return capitalizedString;
}

// Using a parameter
function trim(string, characters) {
  // string...
  return capitalizedString;
}

What is the best practice when coding functions like this? Using this is a clear winner since that's how the native and user utility methods for the .prototypes of String, Array and such work. More importantly, that's how polyfills are done. Plus FBS, which allows very easy consumption of functions coded like this, is already at stage 0.

When the functions in the PO examples should be coded with this anyway, then where is the need for PO?


Also, you shouldn't make your examples this simple. It's almost like you are deliberately trying to hide the truth.

This is the reality of current PO syntax:

console.log(getSomething() |> _ => trim(_, '-') |> _ => words(_, /[^, ]+/g)
|> _ => pad(_, 20, '-'));

Even if there is a need for PO, the syntax should change:

param1 |> func(param2, param3)

console.log(getSomething() |> trim('-') |> words(/[^, ]+/g) |> pad(20,
'-'));

Then it becomes something very close to FBS and doesn't look weird when it's used with it everything else:

'something'
  ::trim('-')
  .concat('asd')
  |>repeat(3);
# Kevin Smith (3 years ago)

It also seems like the current proposal for PO makes it awkward when mixing methods and piped calls in a chain.

(someArray .filter(...) .map(...) |> someOtherArrayOp ).forEach(...)

Note the extra parens before the "forEach" invocation.

# Coroutines (3 years ago)

On Sun, Dec 13, 2015 at 10:32 PM, Kevin Smith <zenparsing at gmail.com> wrote:

(someArray .filter(...) .map(...) |> someOtherArrayOp ).forEach(...)

Note the extra parens before the "forEach" invocation.

For the record I think a pipe operator is silly - I wish | were available because that is the only operator I will ever think of as a pipe.

That said, if you want to get silly - you could have a pipe operator which uses the current this, and one that compiles to use .call(lhs_argument, ...).

<> (uses the left-hand-side value as this for the next function) |> (simply passes the left-hand-side value to the right preserving this)

Yes. I"m using the spaceship operator for this because why not, I already think this is silly.

[ 'a', 'b', 'c' ] <> filter(...) <> map(...) |> someOtherNonArrayFunc
<> forEach(...)
# Bruno Jouhier (3 years ago)

await could be handled by with contextual lexing: handling |> await as a single keyword.

Another solution would be to collapse the two into a variant of the pipeline operator: |await>, |!>, ...

This could be an opportunity to revive the syntax sugar that was proposed in strawman:concurrency

// concurrency strawman
lines = fs.readFile!('./index.txt').split('\n');
// pipeline operator
lines = './index.txt' |!> fs.readFile |> str => str.split('\n')
# Marius Gundersen (3 years ago)

Do we really need to add support for await in a pipeline syntax sugar when there already is a piping support in .then()? If you need to await something in that chain, then just use .then().

let result = await fs.readFile('index.txt')
  .then(aSingleParamFunction)
  .then(anotherSingleParamFunction)
  .then(x => multiParamFunction(x, 10));

I really don't see much gain in adding this syntax when there is already a FBS proposal that covers most of the cases. The pipe operator only supports single param functions. With multiple params you either need to use fat-arrow (while FBS handles multiple params) or you need a special function that is curryable. So now we either need functions that is curryable (for |>) or a function that relies on the this value (for FBS),

so libraries will probably need to be specially written for whichever proposal is added to the spec. It looks to me like FBS adds a lot more value than |> does.

# Kevin Smith (3 years ago)

Sidebar: thanks to Isiah Meadows, the FBS proposal now also supports constructor wrapping via the ::new syntax:

let factory = SomeClass::new;

# Alexander Jones (3 years ago)

Correct me if I'm wrong but doesn't your example here result in extra turns of the run-loop, even for synchronous functions?

This whole thing feels a bit unilateral to me and at risk of over-engineering a solution we'd maybe do well to consider what other languages are doing, e.g. en.wikibooks.org/wiki/Haskell/do_notation

# Isiah Meadows (3 years ago)

Or at least something that can come next iteration. At least the spec is written :)

All that's left is the endless bikeshedding over the syntax :D (Hasn't it been like a few months people have been debating syntax?)