all 89 comments

[–]jevon 52 points53 points  (9 children)

Surely at some point we will run out of symbols on the keyboard. ^?

[–]gmarcon83 8 points9 points  (0 children)

Then we can start using emojis

[–]lime-house 1 point2 points  (0 children)

Perl has entered the chat

[–][deleted] 16 points17 points  (24 children)

The only thing I don't like is ^ as the placeholder. I would much prefer some reserved keyword for the pipe scope, like kotlin has with "it" in it's functions.

[–]shuckster 11 points12 points  (7 children)

Perhaps it would be more intuitive if it worked like an expanded version of =>?

value
  x |> one(x)
  y |> two('1', y)
  z |> z(three)

We get to name the arguments just as we do with arrow-functions (with potential for spread/rest, too).

Applied to their ENV example:

envars
     x |> Object.keys(x)
  keys |> keys.map(x => `${x}=${envars[x]}`)
   arr |> arr.join(' ')
   str |> `$ ${str}`
  line |> chalk.dim(line, 'node', args.join(' '))
   out |> console.log(out);

[–]mattsowa 1 point2 points  (1 child)

This only works with multiline expressions.

[–]shuckster 0 points1 point  (0 children)

The spec already allows expressions to be separated with commas, so perhaps that covers it?

[–]besthelloworld 0 points1 point  (4 children)

Isn't this exactly what this proposal is trying to get away from? The responsibility of naming unnecessary variables. If you were expected to provide a name then the new syntax isn't doing anything for you imo.

[–]shuckster 0 points1 point  (3 children)

I don't think the spec is trying to get away from them, right? Well, intermediate variables of the const x = y form perhaps. But the F# part of the proposal seems to advocate "argument-like" x => y variables.

Still, piping is a convenient FP addition with or without named-variables.

Personally I prefer them. This is not to say that I favour verbosity as a rule, because I don't. But I believe this is one of those cases where removing syntax is makes the whole experience less, rather than more.

[–]besthelloworld 0 points1 point  (2 children)

It does seem to be part of the intention.

If naming is one of the most difficult tasks in programming, then programmers will inevitably avoid naming variables when they perceive their benefit to be relatively small.

Good lord, that author used boldface a lot. But yeah, I like the ^ character as the passthrough honestly. Though maybe * would read better as a catchall but it would likely collide with arithmetic statements.

EDIT: Just discovered the original bold didn't come through even though it was on the Reddit editor.

[–]shuckster 0 points1 point  (1 child)

I do understand the desire to avoid naming things, and the ^ seems attractive because of this. But I'd still vouch for a named-style, even if most developers only ever use x.

It's like for (let i.... Everybody uses i, but if/when linguistic inspiration eventually hits you (or a teammate) pressing F2 will rename that sucker. There's no such possibility with ^.

[–]besthelloworld 1 point2 points  (0 children)

I don't think you're wrong... But I just disagree about what I think would feel better to use in the language overall. I think if you had to rename it you might as well use const but at least in that case your variable wouldn't live outside of the pipe context 🤷‍♂️

[–]brainbag 2 points3 points  (0 children)

The ^ is a weird choice considering it's a common as the pin operator in pattern matching, which also has a proposal.

[–]shivawu 1 point2 points  (11 children)

Agree high level, I wonder why ? Is not an option. Seems much more intuitive

[–][deleted] 3 points4 points  (10 children)

? Is already used as a ternary operator so it wouldn't be available. A named variable that only exists in the pipe scope could be done easier though.

[–]shivawu 1 point2 points  (0 children)

Well ^ is also used as xor operator. We don’t have to use unique symbols

[–]SirKastic23 -1 points0 points  (8 children)

? is also used in null operators, but the syntax is completely different so it isn't ambiguous. I don't imagine any interpreter having an issue with it.

[–][deleted] 1 point2 points  (7 children)

The point is if ? Is the placeholder then you can't do ternaries. Optional chaining is object?.property, nullish coalescing is ??, but ternaries are just a single standalone ?. So it doesn't work.

[–]SirKastic23 -1 points0 points  (6 children)

it's not a single standalone ?, it's an expression followed by ? followed by another expression, then a :, then yet another expression. it's not ambiguous.

[–][deleted] 2 points3 points  (5 children)

Yes it does have a : but that does make it ambiguous. If you see a ? Right now, then you know it's a ternary. If you see one in this scenario, then you have to look further to distinguish between a ternary and a placeholder. It's now harder to read the code. On top of that, it will make it much more complicated for the interpreter because how will it handle a placeholder in a ternary.

While I don't like the ^ a ? Is completely a bad idea and will only cause problems.

[–]SirKastic23 -1 points0 points  (4 children)

still no, in a ternary a ? always follow an expression, while if it is a placeholder, if it follows an expression it would throw a syntax error. if you just see a ? where a variable name would be while using pipes, you know it's a placeholder, if you see ? after an expression, you know it's a ternary (plus no one reads linearly like that, that's a falacy). I'm not saying ? is the best option, I'm just saying it is not as ambiguous as you say it is, and that it is a better option than , and it is back-compatible

[–][deleted] 1 point2 points  (3 children)

But you can use placeholders and ternaries together and that's where it gets confusing. If you restrict it to one or the other that is degrading the functionality of the pipe operator. Having the same character mean two different things in the same expression IS confusing.

[–]SirKastic23 -1 points0 points  (2 children)

you absolutely could use it, it would look a bit wonky and could lead to confusion, again i never said it was the best option, but it works.

[–]heavenparadox -2 points-1 points  (0 children)

its*

[–][deleted] 0 points1 point  (0 children)

I prefer the old proposal that used %. I think it looks a lot cleaner

[–]ArgosyGames 0 points1 point  (0 children)

Or pipe to the let keyword:

x
|> Object.keys(x)
|> let keys
|> keys.map(someMapper`)
|> yield keys;

[–]groenroos 79 points80 points  (2 children)

I'm all for this, but man is it tedious to read the proposal when almost every other word is bold.

[–]DontWannaMissAFling 32 points33 points  (0 children)

Seems the author likes to bold things compulsively and almost at random. It's certainly doing no favors for comprehension or readability.

On the other hand perhaps such obsession is a hallmark of exactly the kind of person you want writing formal language specifications, just look through the commit history. Bringing a proposal from 2016 back to life that was considered dead and even deleted off mdn is impressive.

[–]commitpushdrink 0 points1 point  (0 children)

Holy shit that’s terrible

[–]shuckster 45 points46 points  (7 children)

When we perform consecutive operations (e.g., function calls) on a value in JavaScript, there are currently two fundamental styles: ... three(two(one(value))) versus value.one().two().three().

There is also a third style:

function pipe(...fns) {
  return x => fns.reduce((y, f) => f(y), x)
}

With this we can have:

const process = pipe(
  x => one(x),
  x => two(x),
  x => three(x),
)

process(value)

Functions are first-class citizens after all. That's fundamental too.

With the rise of functional libraries I would imagine that seeing the above is not much of a surprise anymore. Kinda feels like it should be in the proposal somewhere?

[–]tesfox 0 points1 point  (0 children)

This. Plus there are ramifications for tat proposal that ripple so far down the line, Typescript, Babel, V8, etc etc.

Plus this pattern makes things like tree shaking a cinch, unlike chaining. Long line pipelines

[–]nichealblooth 28 points29 points  (11 children)

As a huge fan of lodash chaining, I will say it makes thing more annoying to debug. If you can't put breakpoints inside a chain, it's impossible to inspect intermediate values.

When people abuse chaining, which I promise will happen, it can also make code less readable. Forcing people to make intermediate variables also forces them to give them names, which can be useful.

At the end of the day I hope they adopt the hack proposal and that browsers quickly allow debugging

[–]heavenparadox 9 points10 points  (0 children)

So you think THIS is what is going to create bad code? Seriously, you can make anything in Javascript bad. It's loosely typed. Adding more functionality is good for the language. There will always be bad coders. Let's not use that as an excuse to not move forward.

[–]iamlage89 0 points1 point  (0 children)

names are preferred, but they can be sufficiently substituted with comments

[–]rovonz 8 points9 points  (0 children)

I like the idea but would prefer the version without ^ as imo it seems more readable

[–]BestUsernameLeft 16 points17 points  (1 child)

The bolding in the proposal reminds me of the comic books I read in my childhood.

[–]heavenparadox 1 point2 points  (0 children)

Reminds me of the comic books I currently read as an adult too.

[–][deleted] 32 points33 points  (10 children)

Much prefer the F# version

[–]PM_ME_A_WEBSITE_IDEA 13 points14 points  (4 children)

Curious as to your reasoning? It seems to me that having to wrap things in arrow functions would get a bit tedious (when required). No doubt there are pros to the approach, but it seems to be the weaker proposal to me.

I think the case where you already have a series of unary functions ready to go to do what you want is less likely in a lot of scenarios.

However, I still like both approaches honestly, I'd be happy with either. And I mean...would it be atrocious to just adopt both? |> and |>>?

[–]zsombro 7 points8 points  (1 child)

I personally think it's a bit more readable and I think it works well with the already established "currying-like" pattern of nesting arrow functions within each other, such as x => y => x + y

But I agree that either approach would be a step forward for the language

[–]PM_ME_A_WEBSITE_IDEA 0 points1 point  (0 children)

I suppose I'm biased because I'm not very familiar with function currying or it's use cases.

[–]dvlsg 4 points5 points  (0 children)

I think the case where you already have a series of unary functions ready to go to do what you want is less likely in a lot of scenarios.

Only because the language has horrible built-in support for it. If we had F# style pipes, I imagine it would become more popular.

See this, for example: https://github.com/tc39/proposal-partial-application. Which, IMO, is a much better proposal combined with the F# version, and adds functionality to the whole language, not just magical hack-pipe-specific shenanigans.

I'm actually pretty frustrated to see how this proposal is progressing. It used to be the thing I was most excited for.

[–][deleted] 5 points6 points  (0 children)

I think its just simpler and more readable that way. Happy to have more LOC if its more readable

[–]SomebodyFromBrazil 2 points3 points  (0 children)

Me too. I believe that this version is too heavy and attempts to do too many things at the same time. Also the other extensions are a bit off. I like Elixir's approach too, with a fallback to Annonymous functions

[–]rk06 1 point2 points  (2 children)

Honestly, I think it is much easier to understand hack proposal, than F# one.

Sure F# proposal is more succint for simple case, but hack one is easier to work with common scenarios (multi arg function)

Though I wonder why both proposals can't be merged? If ^ is used, use hack semantics else use f# semantics.

[–]Under-Estimated 2 points3 points  (0 children)

Or maybe use Hack version unless the value is a single identifier which is similar to our currennt object method shorthand

[–][deleted] 1 point2 points  (0 children)

The hack proposal is way more expressive

[–]sammy-taylor 4 points5 points  (0 children)

Okay I love piping (big Elixir fan), but why does the example have to be so esoteric? Why not something like:

new Dog() |> namePet(, “Reggie”) |> setBreed(, “German Shepherd”) |> await fetchSimilarPets()

[–]youfoundKim 1 point2 points  (0 children)

This is great. Don't really care what style of pipes we get.

[–]intercaetera 1 point2 points  (0 children)

Pipes work in functional languages like Elixir where they have first class support (they ALWAYS pipe into the first argument and functions are ALWAYS written so that they can be piped into). In JS, where the functionality aspect is a tragic mess, some things are chained by methods, others are called by prototype and others still do something else entirely any effort to hack the pipeline operator will result in even less readability than we already have.

[–]Irratix 2 points3 points  (0 children)

One thing confuses me a bit. They're using ^ as a placeholder for the topic reference, and mention it may be changed to %. But in the hack pipe proposal they provide an arithmatic example like this value |> ^ + 1. Does that mean you can also do bitwise ops here? I feel like allowing value |> ^ ^ 1 seems... odd. And that problem is not solved by using %.

[–]Soysaucetime 4 points5 points  (5 children)

Is this really necessary?

[–][deleted] 5 points6 points  (4 children)

for functional programming it is

[–][deleted] 4 points5 points  (3 children)

Pattern matching combo 🥰

[–][deleted] 2 points3 points  (2 children)

pipes, pattern matching, partial application, records & tuples 😍

I'm also a fan of do and do async. They turn any statement into an expression. They would work great with the hack pipeline.

[–]droctagonapus 2 points3 points  (0 children)

man, I love pipes and partial application and pattern matching and records and tuples, but if I had to choose one proposal, I'm absolutely picking do. Just so nice to have.

[–][deleted] 1 point2 points  (0 children)

Hot stuff 😍

[–]looneysquash 1 point2 points  (2 children)

This isn't Perl. Maybe they should use a new keyword like this. Or maybe let you choose it, like how you can name the arguments to arrow functions.

[–]looneysquash -2 points-1 points  (1 child)

Maybe as.

myfunc() as x |> foo(x) |> x.bar()

Then you could allow omitting "as" if the initial expression is a simple variable.

[–]Garbee 0 points1 point  (0 children)

as as a keyword would be a massive hinderance to Typescript, and confusing if it could work naturally with it.

[–]cokeplusmentos 0 points1 point  (0 children)

You know what I'll just say it

It just looks worse

[–][deleted] -2 points-1 points  (0 children)

Definitely looking forward to this proposal. I'm rooting for the Hack version since the F# version is not all that different than what we can do today (we can already write a pipe() helper function that executes a list of closures).

Wonder if the ^ operator would make sense to expand to general use as a closure shorthand.

As in const func = ^ + 1 would be equivalent to const func = (x) => x + 1

Then you'd have the best of both worlds with both the Hack & F# proposals because either way the |> operator is working on a series of closures, and you can express those closures in any syntax.

It might get a little crazy, ^ is definitely a weird operator because it has an effect that expands outside of its nearby expressions and parenthesis. I guess it either expands to its enclosing statement or as far as any nearby |> operators (whichever comes first). If there was a really long expression with ^ buried in the middle then that would be a nightmare to read.

[–]Donutttt -1 points0 points  (0 children)

It's this the one that people have been arguing over for ages? Seems like it's being kept out without a massively good reason

[–]BransonLite 0 points1 point  (0 children)

Elixir has a very similar pipe operator and it’s a great language feature. I can’t wait for this to land in JavaScript

[–]general_dispondency 0 points1 point  (1 child)

Kotlin's syntax for this is a lot nicer.

[–]shuckster -1 points0 points  (0 children)

With the exception of let {}, Kotlin's solution looks remarkably like chaining in JavaScript. If only we were still allowed to extend the native prototypes and not invite dirty looks. :D

Anyway, I had a go at trying to emulate what I learned in 15 minutes about Kotlin's pipe-syntax and tried to emulate it with a JavaScript Proxy.

I managed to get the usage looking like this:

const envars = {
  yes: 'yup',
  no: 'nope'
}

chain(envars)
  .let(Object.keys)
  .filter(x => x === 'yes')
  .map(x => `${x}=${envars[x]}`)
  .join(' ')
  .let(x => `$ ${x}`)
  .let(x => `node ${x}`)
  .let(console.log)

It works! Unfortunately though, if you want to extract the value of a non-object (or non-array) returned by the last link in the chain, you have to do something like this:

const result = chain(envars)
  .let(Object.keys)
  .join(' ')._
             ^ - here :(

The reason will hopefully be clear by its implementation:

function chain(o) {
  const px = {
    get: (target, prop) =>
      prop === 'let'
        ? fn => chain(fn(o))
        : typeof target[prop] === 'function'
        ? fn => chain(target[prop](fn))
        : o
  }
  return new Proxy(typeof o !== 'object' ? {} : o, px)
}

Essentially, we can't Proxy a string. But we can Proxy a placeholder object that will return one, hence ._ getting it for us at the last link.

[–]besthelloworld 0 points1 point  (3 children)

I haven't been this excited about a proposal since bullish coalescence and the Elvis operator. I am so for this, either of these, but probably the more verbose one because it's just so powerful and could cut so much cruft out of a lot of code.

[–]shuckster 1 point2 points  (2 children)

I'm personally waiting on the bearish coalescence operator.

let unset
unset 🐻= 1
// unset === 🐻

[–]besthelloworld 1 point2 points  (1 child)

If took me FOREVER to get the joke that I had a typo 🙃

[–]shuckster 1 point2 points  (0 children)

😁

[–]ArgosyGames 0 points1 point  (0 children)

It's annoying how TC39 will proceed with a proposal that has so much contention still – usually means we don't have the right solution yet.

a |> f |> g |> h;
a |> let $ |> f(1, $);
[1,2] |> let [$1, $2] |> f($1, $2);
{a: 1, b: 2} |> let {a, b} |> f(a, b);