107 comments

[ 5.0 ms ] story [ 215 ms ] thread
I've been experimenting with this via a babel plugin for a while.

The difference in ergonomics is huge, and it's readable too!

At first, I was so excited to try it, I wanted to "simulate it" via some library I developed: https://github.com/egeozcan/ppipe Then I realized it was super hard to add typings for it (I use Typescript nearly exclusively these days).

Anyway, I can't wait this to be adopted by Typescript!

This idea has been toyed with for a decade. Im pretty sure lodash still has a pipe method somewhere. I actually prefer the `.pipe` function over the proposed operator, but we’ve had that available for 10+ years with very little adoption.

Unless the new operator was wildly easier to use (it’s not), that adoption says everything we need to know about the need to add this feature to JS.

I did develop a library and am still of the opinion that the native implementation is vastly superior.

You cannot use .pipe unless you break other code and start changing prototypes of all the things, or you wrap the result every time.

So when I'm writing an expression, I need to go back and write pipe(expression).pipe(...

Also the placeholder is much easier to use than using something like my solution (const _ = pipe._) for it. You don't need to remember importing/defining it!

I was even toying with a crazier version (notice the extra p, yeah I suck at naming things): https://github.com/egeozcan/pppipe/

The only reason that exists is to prevent the clutter of writing .pipe all the time.

Yet another reason is, you'd get autocomplete with a native implementation, and also TS support!

Also, it's been toyed for surely more than a decade, even my library is 6 years old (older if you count my previous non-public attempts).

You can type it, take a look at pipe and pipe1 in [0].

[0] https://github.com/preludejs/generator/tree/master/src

Mine is a bit more complicated, unfortunately (me from 6 years ago had so much ambition, and implemented all the things)

Also this:

https://github.com/preludejs/generator/blob/master/src/pipe....

Wait, what? :)) I'm not ready to deal with something like that (just to make sure it doesn't come as arrogant, I mean I just don't trust myself)

Yes, pyramid of hell. I said it "can be typed", I didn't say it's "pretty"! :)
LiveScript (https://livescript.net/) has this, amongst many other things (mostly CoffeeScript-inspired). I used it quite a bit and enjoyed it a lot.
Yes, LiveScript was like CoffeeScript in good.

I really liked that it was so concise.

But somehow static typing was deemed more important an, so it never gained traction.

I was recently made aware of D's Uniform Function Call Syntax (UFCS) which is similar but not exactly the same:

    void sun(T, int);

    void moon(T t)
    {
        t.sun(1);
        // If `T` does not have member function `sun`,
        // `t.sun(1)` is interpreted as if it were written `sun(t, 1)`
    }
The nice thing here is that no placeholder like % is needed and it still works with functions that expect multiple parameters.

Docs: https://dlang.org/spec/function.html#pseudo-member

Interesting, it's also general enough to cover extension methods where languages C#, Kotlin and Scala have special constructs for.
D’s way is far superior because as the caller you don’t have to worry about whether you’re calling a method or an external function, it looks the same. Not sure it would be possible to tack this onto a dynamic language like JavaScript though
On using intermediate variables

> "On one hand, that’s more verbose than piping. On the other hand, the variable names describe what is going on – which can be useful if a step is complicated."

If the only downside is that it's a little verbose then I still don't see the benefit of having pipes.

(comment deleted)
You can instead just use F# today for Javascript via fable.io
Or PureScript with `#` (or any other symbol that pleases you, it's just a trivial definition away ;), or ReScript with `->`
(comment deleted)
> const y = h(g(f(x))); This notation usually does not reflect how we think about the computational steps.

Do others feel this way too? I have to admit I don’t really see it. It seems like a very subjective syntactic decision to me. Why not assign each call to a variable for readability? GC overhead? I guess naming things is hard, so sometimes you’re just going to do things like:

halve(double(triple(5)))

But honestly this doesn’t seem that hard to read to me for reasons that are perhaps beyond me. Granted, JS is my mother tongue, so maybe I’m just brainwashed.

I think I’m also hung up on the use of % because it already has the job of being the remainder operator. Are there other examples in the language where operator behavior changes in different contexts that are similar to this I’m missing (aside from the obvious order of operations or operator associativity)?

People read left to right, not inside out. Local vars can make reasoning harder, because they are visible throughout the current scope.
Only “half” the world reads LTR.
Everyone who uses JavaScript reads most of JavaScript left to right.
> Why not assign each call to a variable for readability?

The problem is that if you use a variable, you either:

- use a new variable each time, therefore you have to think of appropriate name each time (and having a lot of variables does not necessarily make code more readable)

- or you have to come up with a rather generic name of variable, like "result" or "output", otherwise the name will not make sense until the very end where the last value is assigned

Let's say I want to have a list of active products from some category. With option 1 it would be:

  const allProducts = Product.all()
  const activeProducts = allProducts.filterActive()
  const activeHouseProducts = Category.match(activeProducts, "house")
with option 2 you'd do:

  var activeHouseProducts = Product.all()
  activeHouseProducts = activeHouseProducts.filterActive()
  activeHouseProducts= Category.match(activeHouseProducts, "house")
and with pipe operator you can do:

  const activeHouseProducts =
    Product.all().filterActive() |> Category.match(%, "house")
FWIW it seems like option 1 should actually be

  const activeProducts =
    Product.all().filterActive()
  const activeHouseProducts = Category.match(activeProducts, "house")
> are there other examples in the language where operator behavior changes in different contexts

The newish Elvis operator

The use of the term "pipe" definitely implies a sequential pipeline, and in that sense I agree with the author.

But it really only manifests after a few function calls, at which point you're forced to find the initial value and recursively parse the methods. The example is probably unrealistic and not real world, but it's a speed bump I've encountered in real code.

Per your example - which is definitely less abstract - it would still feel more intuitive to see

5 | triple() | double() | halve ()

My issue is the syntax in the proposal, which may feel like another language's implementation, but doesn't really feel like JavaScript. Mentally thinking about JS as an "everything is an object" paradigm, just having a breakout-and-apply chain that looked closer to a dot operator would be more native feeling.

('25').|parseInt(%).|math.Sqrt(%)

Other languages imply the pipe "direction" pass-off with syntax like -> but JS has traditionally eschewed that with more apply-transformation-in-place syntax.

I don’t support the pipe operator. I want my code to look like it came out of a beginners book. Esoteric syntax makes it hard for somebody new to the language to understand your code. Codegolf is cool but not helpful
"programming shouldn't evolve, it should look like what i learned N years ago"? ;)
That’s a strawman. Is it “evolution” if it makes the language harder to learn, with no practical benefits other than “it looks cooler”?
I believe "harder to learn" is highly subjective in the same way that Japanese is hard to learn for a native English speaker but easy to learn for a Chinese.

Granted, Haskell and Scala are pretty "rich" and not beginner-focused.

OTOH, a language like Elm has a pipe operator and manages to keep things pretty simple.

Now does javascript need the added complexity? Tough call.

> Japanese is hard to learn for a native English speaker but easy to learn for a Chinese

This isn't to detract from your overall point but I study Japanese and many of my fellow students are Chinese, they certainly don't find it easy, even with the advantage of being able to infer most kanji. Korean to Japanese might fit the example better.

Interesting. The few Chinese students I had in my Japanese class back at university were so much faster than the rest of us.

I agree than Korean to Japanese might be even closer.

(comment deleted)
Well, if we assume that as they can read better they can prepare better, and that they’re relatively time rich as students; whereas in my group we all work, have families etc, it might be harder to make that advantage count?

Whatever the reason, I do envy their kanji skills!

that's fair, i was in a spicy mood.

there's "esoteric" as in fundamentally complicated, and """esoteric""" as in syntax that's unfamiliar but easily explained w/ a quick web search.

i believe "it's nicer to express some things this way" is a very important practical benefit. for me, "being expressive for experienced users" is ultimately more important to optimize for than """learnablility""", scare quotes intended. because beginners will be beginners for a while, and then they won't. (i dislike e.g. Go for this reason, but let's not get sidetracked)

(before you or someone else asks: i've tutored many beginner/first-time programmers, and yes, i still think a bit of syntactic sugar won't hurt them)

The practical benefit is that, unlike say Lodash's chaining, it is outwardly extensible, you don't need to write every function into a big object (and worry about naming clashes).

  _.chain(inputSet)
      .filter((x) => x >= 0)
      .map((x) => x * 2)
      .thru((e) => new Set(e))
      .value()
Assumes chain returns a object with {filter, map, thru, value}, and if you want a new operation you need to assign it to that object, which means the library that returned the original object must expose that too, and everyone knows "Prototype Extension Is Bad" since you could come up with a method name that someone else came up with too, then you'd override things and introduce bugs.

With chaining (even ignoring the % Hack syntax for placeholder expressions) you can use functions from anywhere: local variables, other objects, anything you want, even if the library that provided the original data is not viable to modify, since it doesn't need to be modified.

Another way of approaching this extension problem is, well, with extension members, like Kotlin or C#, where you can declare free functions to operate on a "this" of a specific type, and then you can use it exactly as if it was a method of the type. Of course, JS is dynamically typed so that wouldn't be viable in this case.

Finally, I think that for JS specifically, a pipe function is enough. Sure you have a few more commas around but it's fine. I could see the appeal of having an operator in TypeScript, since it would mean that types would be preserved much better.

You can make a similar argument for JSX, private properties, etc. The point is that there will always be use cases that see massive improvements from a feature, but that doesn’t mean it’s worth adding it to the language and paying the price in specification and implementation complexity, surface area and learning curve. Especially if it doesn’t enable any new use cases.

If the 5% of people that benefit can get 90% there using userland libraries, the cost of that 10% improvement should not be bore by the other 95%.

> no practical benefits other than “it looks cooler”

That is a very ignorant take and only shows that you haven't taken the time to learn the first thing about functional programming.

I think this argument makes sense in _some_ usecases, but definitely not everywhere.

There are genuine places where a FP approach to API/DSL design makes sense and writing code that abstracts away from concrete parameters and focuses on composition is important. Operators for function application and composition can now be used for a similar concepts on a higher level of abstraction.

Suddenly 'esoteric' syntax helps clarify your code and forces you to focus on meaning and intent, not syntactic details of your PL.

I'm a firm believe in "keep it simple" and "don't be clever" when it comes to code I have to maintain. But at the programming language level I want powerful constructs. I want to maintain simple and straightforward code in a powerful language, not the other way around.
It's not esoteric if you are a functional programmer.
Good news, it will look like it came out of a beginners book, as the pipe operator gets added to beginners books. Just like arrow functions.

Your argument at this point is "I don't like it because it's new."

You should possibly instead argue on the merits of what it is.

Simple syntax, and straightforward behavior.

Simple syntax, like how '=>' is a two-character ASCII art 'fat arrow', and not anything to do with logical equivalence. That's how you syntax for beginners!
Eh, programming re-uses tons of punctuation a beginner needs to learn anyways. Semicolons don't split sentences, parathesis change depending on where it's used, commas seem random.

This concept even gets generalized itself with the concept of operator overloading in some languages, etc.

You have no idea how much it improves the readability and understandability of code.
Saying this about JavaScript of all languages... The pipe operator is about the least likely to be confusing to a beginner.

And it's hardly "esoteric" when it's been around for decades in well-established languages (including bash, for God's sake). Just like the other commenter, you're only showing your ignorance of other paradigms here. Just because you were introduced to something else first, doesn't mean it's automatically the most intuitive way for everyone forever.

> including bash

bash scripts famous for being readable and maintainable :P

I fully agree with your sarcasm, but you probably know that that was not my point.

But seriously, I don't see what's so controversial about it. It's just "do this, then take the result and do that, then take the result and..." Everyone understands this intuitively. Plus, lots of libraries already do method chaining, which is just a crappier and inconsistent version of piping anyway.

I disagree. A true begineer is unlikely to be familiar with bash or these other languages.

Syntax like + - / * () = etc. are not esoteric since, for the most part, they work similar to math they learned in school.

But => and |? They are esoteric. As a beginner you have no idea what they will do without study.

> for the most part

That's the important bit. If they always worked as expected, I'd agree. But they don't. I could give plenty of examples but I'm sure you know about them.

> A true begineer is unlikely to be familiar with bash or these other languages

I'm with you on Bash, but not necessarily about "these other languages". Lots of people are being introduced to functional languages first, and if you don't think so you're probably just old.

I also don't assume a beginner will just be given code without any guidance. What do you think about dot notation? If they can/can't understand `foo().bar().baz()`, then surely they can/can't understand `foo() |> bar() |> baz()` all the same?

Actually the "lack of familiarity" argument is the best argument for introducing it in JavaScript: It's the language beginners are learning nowadays, so if it has feature x as a prominent feature, in a few years everyone will know about it.

> … and if you don't think so you're probably just old.

I resemble that remark!

(apologies to all; too good to pass up)

=> also comes straight from math learned in school. There it's written → (for function signatures), and (the more rare) ↦ for function definitions.
Unfortunately we already have arrow syntax, which is as esoteric as it gets. I think the idea of arrow syntax is good, but the chosen syntax sucks.
js is already a mixed bag of stuff, at least the pipe is usefull in making code cleaner especially compared to other features like:

1. support for class because you know real programmer don't like prototypal inheritance and prefer to type the word class in their code. In the meantime, let's make a compiler to translate that code onto something an old engine will also understand, yeah that sounds like an amazing idea and tada babel is now the centerpiece of everything

2. so many way to create a for loop, put a few footguns with the for in syntax, mix it with a few functions in the array proto cleverly named: forEach and map so now we need a linter to enforce a style

3. let's put some await async everywhere but forget about cancellation because things can't go wrong anyway

4. let's make different standard for packaging and import with workaround everywhere so there's no code reuse in between front and backend code even though the primary arugment for nodeJS in the early day was code reuse

5. let's not talk about code that rely on null == undefined to be true and Number(null) to be 0 but Number(undefined) to be NaN, ....

...

We have WASM now. At this point people could just make their dream version of Javascript, with pipes, private class members, abstract factories and the like, and leave JS alone, it’s been damaged way too much already.

</rant>

> it’s been damaged way too much already

Why? It's one of the most comfortable languages for me.

Some parts of it are objectively bad, sure, but why even touch those parts when the language constantly gets updated and modernized... I'm saying this as a developer who mainly writes C# and Go these days.

With JS, without dabbling in wasm wizardry, I can open a console in the browser and directly get an environment with crazy interaction possibilities.

If I'm writing something for the long run, I can use a lighter compile-to-JS language like Typescript (my fav) and when there's an error, I can directly map what's going on to actual source code without even using source-maps. In addition to that, admittedly like many other languages, I can write 2 lines of it in a file, double click the file, and have a web server running! It's amazing that JS gets more and more love, and there are many of us caring for it.

The whole rhetoric that "JS is bad" is getting old. People talk down JS then start using others where white space count matters... Yeah it's all preference but that is my point as well. If you don't like JS direction, you can use your favorite lang via WASM, and let JS get developed in a faster pace as well.

So TL;DR: Why not think the other way around the issue!

The lodash equivalent, which I missed, is something like this:

  _.chain(inputSet)
      .filter((x) => x >= 0)
      .map((x) => x * 2)
      .thru((e) => new Set(e))
      .value()
I think it looks fine, works fine.
To be clear, this is plain javascript. The only lodash does here is the initial _. And you’re right, it does work just fine.
That's not plain Javascript. That's Lodash through and through. The array transformation methods built into Javascript run to completion after every step building multiple copies of the array as each transformation is applied. Lodash's chain and related methods do not make multiple copies of the array during transformation.
Hey, do you have more documentation on how vanilla Javascript handles array transformations? As an example:

  array.filter(f => true).map(f => f);
My understanding is that filter creates a new array, then maps that new array to another new array in final step.

Does lodash do something differently? I'm not seeing anything in documentation for _.chain that says it does something differently.

I mean the continuation pattern used that effectively mimics the pipe operator. I realize that the functions themselves are provided by lodash.
> I think it looks fine

_.chain and .value() could be removed

> works fine.

- it isn’t possible to tree-shake the package and only include the lodash functions that are used - it isn’t possible to have non-lodash functions in the pipeline (e.g. date-fns)

> The proposal “Pipe operator (|>) for JavaScript” [...] introduces a new operator [...] borrowed from functional programming that makes applying functions more convenient in many cases.

I put a brief comparison of the syntax for Shell `|`, Haskell `&`, Elixir `|>` and Clojure `->` in the documentation for my version for C `@`: https://sentido-labs.com/en/library/cedro/202106171400/#back...

There, each operator is a link to the corresponding documentation page if you’d like to know more.

Not really in support for many reasons - but did nobody think about the difficulty of this syntax for even 2 seconds?

A shift + pinky reach for the | followed immediately by a shift + ring finger curl on the same hand? And tbh is is something developers are supposed to use frequently? No thanks.

You do know that there are different keyboard layouts? `|>` is <AltGr> + <w> + <y> (no reason to lift the right thumb from <AltGr>) for me
Not-inconvenient for some doesn't change inconvenient for most. Most keyboard layouts are based on the US's. I have the same complaint about -> as a method locator like PHP does.
Most keyboard layouts do not have symbols anywhere near the places they are on the US layout. Letters, yes, but not brackets, punctuation etc.
I do this every day in Elixir and it’s never been a problem. You get used to it
Decisions, decisions.

On one hand, having a pipe is real nice. Some flows are better represented like thwt. On the other, this example doesn't make sense to me.

const y = h(g(f(x)));

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

How is the hack pipe easier on the eyes and mind? Also went through the rest of the article and I have to say things don't get better in terms of readability.

That said, it's arbitrary. I have no doubt many would disagree with me given how often I see people writing code as if it's unthinkable to have more than a handful of symbols on a row.

    const y = h(g(f(x)))
    const y = x |> f(%) |> g(%) |> h(%)
It get's better with

   const y = 
     g * k + j / w * d - 453368423
       |> (x) => { 4566 * x + 46832 - x * x + 1 / x}(%)
       |> (x) => { x / 4653 + 843 * x *x }(%) 
       |> (x) => { 486543 - x + x * x }(%)
The question is if you often do things like that, though ;)
The example that resonates with me is when the functions have multiple parameters:

    const y = h(1, g(f("world", x, 3 ),true))
    const y = x |> f("world", x, 3) |> g(%, true) |> h(1, %)
More readable, imho:

  const a = g * k + j / w * d - 453368423
  const b = 4566 * a + 46832 - a * a + 1 / a
  const c = b / 4653 + 843 * b * b
  const y = 486543 - c + c * c
I actually do prefer this too.

And when using it for arrays, I'd prefer

    const r = [a, b, c]
              .map((e) => 2 * e*e)
              .filter((e) => e >= 0)
              .reduce((acc, e) => acc + e)
to the new piped version.
I guess it's my day to represent the Smug Lisp Weenie† faction of Hacker News?

https://wiki.c2.com/?SmugLispWeenie

This is what happens when you don't have user extensible syntax, you end up petitioning committees for... another way to write a threading macro.

If you want macros so badly have you considered, wait for it, s-expressions?

> If you want macros so badly have you considered, wait for it, s-expressions?

Actually the only acceptable way to define a 'pipe' (aka. reverse function application) is something like

    x YOUR_SYMBOL_OF_CHOICE f = f x
Every language that needs more than this is already seriously flawed :D
This is true. On the minus side writing tooling for an s-expr language is much much harder. Being able to reliably refactor my typescript code with a key press is a big plus.
It's not supposed to be!

https://shaunlebron.github.io/parinfer/

A better world is possible!

This looks nice but doesn’t seem to come close to the kind of structural understanding of code that you get with statically typed languages with a fixed syntax.
> This is what happens when you don't have user extensible syntax, you end up petitioning committees for... another way to write a threading macro.

There is user extensible syntax, JS users have been using macros for years through Babel. Here's the plugin for the different pipes: https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-op...

Well, you can easily (and not that ugly) curry JS-functions by using arrow notation:

   f(a)(b)(c) -> f = (a) => (b) => (c) => {...}
Here are the two forms being offered for a pipe operator:

  const y = h(g(f(x))); // no pipe
  const y = x |> f(%) |> g(%) |> h(%); // Hack pipe
  const y = x |> f |> g |> h; // F# pipe
Why not use concatenation?

  const y = x f g h;
In that case why not write

h g f x

and keep the ordering of functions the same between the different syntactical structures.

> Why not use concatenation? > > const y = x f g h;

This can't be broken into multiple lines.

   const y = x f
   g h
Will have an implicit ; in between.

   const y = x 
          |> f
          |> g
          |> h; 
Will work as expected, and potentially increase readability.
Because then a missing comma somewhere becomes a weird bug, as opposed to a syntax error.

Languages with implicit string concatenation using no operator have found it to be a common source of bugs, eg python. See https://news.ycombinator.com/item?id=29841560

Does your suggestion allow passing an extra argument into any of those functions? Like the

    value |> someFunction(1, %, 3)
I get why a lot of people here dislike the pipe operator, but I actually love it. The application I work on has tons of obscure javascript processing in the frontend and I can already think of several places where would make the code look much cleaner.

Sadly, it's also stuck in the past so if and when this operator gets accepted into Javascript, it'll take years before the necessary code cleanup can happen.

I like languages with a small instruction set, which is what JavaScript definitely was some years ago, I would say not so much any more.

Even worse it has bits of functionality that are now layered on top of each other - so hoisting still happens unless you use const and let, there is strict mode which exists if you put the use strict declaration or have things in a module, but otherwise you might make a mistake somewhere and have things polluting the global scope that either you thought was impossible or even worse you were not aware that was even a possibility with the language because you were raised in a garden where it was not allowed, only now a bug brought a bit of the wilderness in to you and you don't understand what the heck is going on.

So to escape these problems with the language use linters, but some times people turn off linters etc. etc.

The suggestion to deal with problems of the language seem always to be:

1. Add more syntax, meaning more special cases to remember how things behave with that syntax!

2. Be disciplined and good and you will never experience these problems that the language has. Also all of your team needs to be disciplined and good, and the people who used to be on the team before you joined should be disciplined and good, and thus there should not be any code that is old enough to be written in a way that does not fit our modern definitions of disciplined and good.

You could have programming language with just NAND as "small instruction set".

The reality is that you want "instruction set" that you're using to solve your problems. But other people will want to work on different abstraction levels - yours and theirs desired "instruction set" will vary.

>The reality is that you want "instruction set" that you're using to solve your problems.

yeah either that or by having a small instruction set you can know all the language at a reasonably deep level, but with a large enough language and the need to write maintainable code this becomes more difficult. You may be hit with situations where you need to look up the specification of the language you are supposed to be an expert in.

But you're right, you definitely know better than I what I mean when I say I like languages with a small instruction set.

It helps to have terse code constructs (destructuring) or more sane behaviour (const/let vs var scoping). I don't think constructs like pipe operator, nullish coalescing or optional chaining hampers language to the point that it becomes impossible to know at reasonably deep level. Those are quite fundamental, often quite simple, concepts and many of them are sprinkled around different languages here and there already. TypeScript for example has much harder learning curve. Personally I find things like generators, async generators, tagged templates absolutely essential in day-to-day work and can't imagine working without them. Recent additions to JavaScript language through healthy community interaction were absolutely great IMHO.
The slippery slope toward Scala's operator madness. Much of the value of a language can be its simplicity and grokability by typical devs. Please find a sensible stasis.

Checkout this list of new JS/Node.js features [0] as an example.

Will a world with the following be an improvement? [1]

• ??=

• ||=

• &&=

And don't forget the strife around the Python "walrus operator" := [2]

[0] https://node.green/

[1] https://github.com/tc39/proposal-logical-assignment/

[2] https://realpython.com/python-walrus-operator/

I feel like this is spreading misinformation about scala. Those are not operators, rather methods. It’s syntax rules are fairly simple to reason about.

The problem here is more the growing language syntax complexity of JavaScript

That being said, just because you can name a method ‘||=‘ in scala, doesn’t mean you should. A lot of us scala devs realize that it’s not very readable. Symbols as method names should be used rarely if at all. But One can write unreadable code in most any language with poor naming, and that’s all this is

I <3 Scala a great deal. Wrote it professionally for years and did just as you suggest; set rules about what patterns were acceptable and not. Regardless, the common critique against Scala is that of an impenetrable haven for type astronauts, such as the Admiral Ackbar operator.
This operator proposal has been there for quite some time! Seems like it gets rediscovered periodically and continues to provoke heated discussions.

Personally, I used to be fairly strongly against it, but nowadays I'm not sure if I care. I suppose I'm still not particularly in favor. But maybe I'm biased towards minimalism. I don't dislike the idea of the operator in itself, just how it fits (or rather doesn't fit) into JavaScript. I think it makes more sense in F#.

Four years ago I got really sucked into this topic and thought about this a lot. I had a lot of fun writing down a few counterarguments:

https://djedr.github.io/posts/random-2018-01-25.html

This was partly inspired by this HN thread:

https://news.ycombinator.com/item?id=16193112

It seems that since that time `Function.pipe` appeared as a serious alternative or complement to the operator. Nice!

Besides `Function.pipe` these alternatives mentioned in the article are good:

https://2ality.com/2022/01/pipe-operator.html#using-intermed...

https://2ality.com/2022/01/pipe-operator.html#reusing-a-vari...

The first one is actually the most simple, readable, safe, and has been supported forever. It's just verbose and suffers from the hard problem of naming things. Though sometimes just thinking about a good name for an intermediate variable can lead to good refactoring idea. And intermediate variables provide clean breakpoints -- easier for debugging.

The next alternative solves the naming and verbosity problem. Back when I was into this, I was writing a blog post about this technique. Ended up not finishing it before my interest in bikeshedding faded. Which reminds me to cut this post short and tend to more pressing matters.

It can be fun though.

> For await and yield, we’d need special syntax – e.g.: > value |> await // awaiting a Promise

As much as await is nice and helps code to look cleaner - it should be noted that this example is one of the reasons why language designers have to be careful when adding syntax to support "special cases".

It makes every change down the road just a little bit harder / more complicated.

Also reminds me of Kotlin 'let':

    val y = h(g(f(x)))
is the same as

    val y = x
        .let{ f(it) }
        .let{ g(it) }
        .let{ h(it) }
I have used and loved the pipe operator in Elixir, but always missed using it for functions where the data is the second argument, for example. It's slightly less verbose, but more restricted and more complex to understand. I prefer the JavaScript approach.

In any case, I find it odd using the % as the variable name. Maybe if the operator was x %> f(%), but that probably is an invalid token.

I would go for a split of the currying and proposed solutions. Logically

  y = x |> f2(a, _)
  y = x |> f2(_, b)
are currying, but the implementation doesn't need to create functions any more than if the _ was %. And

  fb = f2(a, _)
  fa = f2(_, b)
are explicit currying which would be nice to have at times, but would mostly use in piped context at first, perhaps to give it a more meaningful or narrowed-scope name.
Yuck, I wish they’d just stop messing with the JS language primitives and syntax already and just let it be. Less is more.

What’s that quote? Something along the lines of:

“Any idiot can make something more complex, it takes true genius and courage to simplify.”

Crawford’s book:

    JavaScript: The Good Parts
Is a very thin book and pretty much sums it up for the actual useful important bits I needed to know. Don’t need to save a few lines here and there with new unintuitive, esoteric operators.

Python also has the same disease as of late, with “features” like the addition of the async keyword and introducing multiple ways of accomplishing the same thing. It’s sad to see previously beautiful, elegant giants grow random trendy warts. They could have remained opinionated, pragmatic, and timeless instead. Could have left async to the mess that is Twisted (which was useful and fine as it was!).

/rant, sorry.

I see Doug Crockford's book as a dark magic tome for the dark ages of JS, but that time has passed. Even the codebase of Crockford's old ESLint has moved on to modern JS.

That book was published right around the release of Node.JS, the iPhone 3GS, and very shortly after came NPM. So the JS community did not have proper modules back then and would use immediately invoked anonymous functions to mutate the global scope to create a sense of namespace modularization.

The retrospective consequence is that valid JS namespaces like _ and $ are no-go zones for the community, and possibly forever so. Modifying the prototype of common objects also did not help (flat vs flatten). Not a harmonious way of relating to the neighborhood IMO.

And on the matter of async, what we used to have was a hack given by the popularization of a Microsoft browser API.

Now I, for instance, would like to see JS getting decent syntactic sugar for function composition and pattern matching.
I have a dream that we will someday leave OOP behind and do everything with algebraic data structures, simple containers, functions with multiple dispatch, and pipes. No more Execution in the Kingdom of Nouns, no more chin-scratching about what noun a verb "belongs to", no more OOP-ified Design Patterns with elaborate object hierarchies, no more inheritance Architecture Astronaut-ery. Just nouns and verbs interacting as equals.