DEV Community

Cover image for TC39/proposal-pipeline-operator Hack-style |> hijacks Grouping operator ( )
Ken Okabe
Ken Okabe

Posted on • Updated on

TC39/proposal-pipeline-operator Hack-style |> hijacks Grouping operator ( )

TC39 proposal Hack Pipeline Operator |> is incompatible with Grouping operator ().

Short version

Grouping operator ( )

The grouping operator ( ) controls the precedence of evaluation in expressions.

The grouping operator consists of a pair of parentheses around an expression or sub-expression to override the normal operator precedence so that expressions with lower precedence can be evaluated before an expression with higher priority.

Test Code 1:

const f = a => a * 2;
const g = a => a + 1;

1 |> f(^) |> g(^);
1 |> (f(^) |> g(^));  
Enter fullscreen mode Exit fullscreen mode

Now we made (f(^) |> g(^)) to be evaluated before other expressions with higher priority.

I've investigated with Babel, and the transpiled result is identical, which means:
(f(^) |> g(^)) is NOT evaluated before other expressions with the rule of Grouping operator ( ).

image

Does the current proposal Hack |> hijack the Grouping operator ?

Test Code 2:

Now I have a log function.

const right = a => b => b;
const log = a => right(console.log(a))(a);
Enter fullscreen mode Exit fullscreen mode

This behaves like identity function: a => a which does not affect to the original code but console.log(a) in the process.

Now, we want to know the evaluated value of (f(%) |> g(%))

1 |> (log(f(%) |> g(%)));
Enter fullscreen mode Exit fullscreen mode

This should be totally fine because (f(%) |> g(%)) must have some value of the evaluation according to:

The grouping operator consists of a pair of parentheses around an expression or sub-expression to override the normal operator precedence so that expressions with lower precedence can be evaluated before an expression with higher priority.

File:Some babel code.jpg

The vanilla JS code is:

var _ref, _ref2, _ref3, _ref4;
const id = a => a;
const right = a => b => b;
const log = a => right(console.log(a))(a);
const f = a => a * 2;
const g = a => a + 1;
_ref2 = 1, (_ref = f(_ref2), g(_ref));
_ref4 = 1, log((_ref3 = f(_ref4), g(_ref3)));
Enter fullscreen mode Exit fullscreen mode

and the result is:
3

Therefore,

1 |> (f(%) |> g(%));
Enter fullscreen mode Exit fullscreen mode

where (f(%) |> g(%)) == 3

1 |> (f(%) |> g(%))
==
1 |> 3

???

and the evaluation value of whole 1 |> (f(%) |> g(%)) is 3
therefore,
1 |> 3 == 3

I have no idea for this hack-pipe-operator, and simply think this has broken the laws of Mathematics, and more importantly, it seems the current proposal Hack |> hijacks the Grouping operator

The Babel implementation

https://github.com/tc39/proposal-pipeline-operator/issues/227#issuecomment-926136875

The Babel implementation has been done by @js-choi (one of the champions of the Hack pipes proposal).

The "reasonable stardard" is the spec proposal: either you learn how to read it (I'd be happy to help, if you are confused about any spec part), or you trust what who can read it says.

Issue

In fact, I made an issue for this.

Does the current proposal override the Grouping operator ( ) with Hack |> ? #229
https://github.com/tc39/proposal-pipeline-operator/issues/229
image

https://github.com/tc39/proposal-pipeline-operator/issues/229#issuecomment-926308352

As had been explained already, parentheses don't work the way you seen to think they do. The result you're getting from Babel is entirely correct and expected, with exactly the same precedence and execution order that you'd get from parenthesizing a sequence of additions.

As this question has been answered, I'm closing this thread.

File:Tc39 terms.jpg

Issue closed with the tag of invalid, well, I don't think so.

@js-choi has explained to me for sure:

https://github.com/tc39/proposal-pipeline-operator/issues/227#issuecomment-926317660

@js-choi explanation

Hm. Well…I don’t think anyone is admitting or covering anything up. But I’ll give it a shot; hopefully this will help a little. ^_^

Parentheses change grouping. But they never have changed evaluation order, which is always left to right, even outside of parentheses. This is how JavaScript (and many other programming languages) have always been:

function f () {
  console.log('F');
  return 3;
}

function g (x) {
  console.log('G');
  return x + 4;
}

function h (x) {
  console.log('H');
  return x * 7;
}

// This will print F then G then H, and it will result in 3 * 12, i.e., 36.
`f() * (g(1) + h(1))`
Enter fullscreen mode Exit fullscreen mode

Note how the f() evaluates first even before (g(1) + h(1)), despite (g(1) + h(1)) being in parentheses. That’s why F still prints first before G and H.

Parenthesized expressions are not always evaluated first; expressions outside of the parentheses to the left are evaluated beforehand. This has how JavaScript (and many other languages like C and Lisp) have always been.

Parentheses change grouping. But they have never changed evaluation order, which is always left to right, even outside of parentheses. This is how JavaScript (and many other programming languages) have always been:

// f, g, and h have the same definitions above.

// This will print F then G then H, and it will result in 3 * 12, i.e., 36.
f() * (g(1) + h(1))

// This will also print F then G then H, and it will result in 7 * 7, i.e., 49.
f() |> (g(^) |> h(^))
Enter fullscreen mode Exit fullscreen mode

Note how the f() evaluates first even before (g(1) + h(1)), despite (g(1) + h(1)) being in parentheses. That’s why F still prints first before G and H.

This is the same reason why f() |> (g(^) |> h(^)) causes f() to be evaluated before (g(^) |> h(^)).

Just like how f() * (g(1) + h(1)) causes f() to be evaluated before (g(1) + h(1)).

It’s just the old left-to-right evaluation rules. There aren’t any special grouping rules here. Parentheses change the grouping, but they never have changed execution order from anything other than left to right.

Hopefully that clears it up a little! I can understand why it might be a little confusing, but it’s just the old rules. Nothing is happening here except for the old JavaScript evaluation/parentheses rules. ^_^

(Actually, perhaps I should also edit MDN’s documentation to make this clearer. Maybe this old parenthesized-expressions-are-not-always-evaluated-first rule is tripping up many people learning JavaScript.)

My explanation

#227 (comment) @jridgewell

Notice the order "two", "three", "four" isn't affected by the parenthesis. Let's try (two() * three()) + four(). Surprise, it's still "two", "three", "four"! But now the final log is 10. That's because the operands are evaluated left to right, and this doesn't change. What changed was the evaluation of the operators.

#227 (comment) @js-choi

Note how the f() evaluates first even before (g(1) + h(1)), despite (g(1) + h(1)) being in parentheses. That’s why F still prints first before G and H.

I observe, again, two of you share the same concept, and there are confusion of concepts . So I will explain to you. Hope it helps, really.

Confusion of concepts

The problem you illustrated is not for inconsistency or limitation of the functionality of the Grouping operator ( ) but evaluation strategy of JS that is eager-evaluation, in this evaluation strategy, f(x) is evaluated as soon as it is found from left to right, yes, you are only correct here.

The only "safe" place is right side of lambda expressions: such as a => f(a). In this case, even it's found by compiler f(a) is safe! will not be evaluated. (the same goes to function statement). Therefore the technique is used to emulate lazy-evaluation. Another exception is true || f(x) but false || f(x) will be evaluated once found. Try it.

Then point is, what you told us is nothing to do with the evaluation order of binary operator or Grouping operator ( ).

The eager evaluation strategy strictly follows the Algebraic rule. Well, if you can find anomaly, show me :) It follows the rule of operators including Grouping operator () with no exceptions. The eager evaluation of f(x) never harms Algebraic expression in JS. If both of you have explained as if eager evaluation of f(x) is the limit of the math in JS. That is the harmful explanation for us.

Sure in your code, we will have F G H or two three four order and so what? Does it break the rule of the math or algebraic structure? Nah..

Parentheses change grouping. But they never have changed evaluation order, which is always left to right, even outside of parentheses. This is how JavaScript (and many other programming languages) have always been:

So, this is a false statement.

The tricky word is: evaluation "order".

Note how the f() evaluates first even before (g(1) + h(1))

So another tricky term should be : "before" or "after"

In mathematics, when we use the term "order" "before" "after", does it mean time series? No way.

Does it mean the order of the line of terminal console?

This logs:

"two"

"three"

"four"

14

Doesn't matter in terms of math structure.

What does matter is dependency network structure.

Binary operation is merely a syntax-sugar of binary function, and when you chain them, you fundamentally compose the functions.

image

For instance, when you enjoy + binary operation, you guys repeatedly told me "left to right", but fundamentally you do

image

This is Fold

https://en.wikipedia.org/wiki/Fold_(higher-order_function)

There are both left and right side fold, and usually we use foldLeft and if it's monoid the result is the same of both side.

As you seen, this is a Graph. Dependency graph

In mathematics, computer science and digital electronics, a dependency graph is a directed graph representing dependencies of several objects towards each other. It is possible to derive an evaluation order or the absence of an evaluation order that respects the given dependencies from the dependency graph.

Remember "evaluation order" derived from the dependency graph or structure is completely different concept of "time order" of executions.

Hack pipe

Hack pipe, on the other hand, unlike eager evaluation strategy, this does break the math structure, and again this one overrides Grouping Operator ( ). I meant in dependency base. This problem is elaborated in my previous post #227 (comment)

I don't think it's on purpose, but the confusion of concept of Evaluation strategy and Algebraic structure is harmfully explained to justify the false design of Hack pipe that overrides the operator with the highest precedence in JS.

I will maintain my claim:

Does the current proposal override the Grouping operator ( ) with Hack |> ? #229

Deletion of my explanation

@js-choi

It’s just the old left-to-right evaluation rules. There aren’t any special grouping rules here. Parentheses change the grouping, but they never have changed execution order from anything other than left to right.

So this is a false statement.

So why there's no link to my explanation? Deleted by them.

File:Delete comments.jpg

https://github.com/tc39/proposal-pipeline-operator/issues/227#issuecomment-926737650

I have just deleted several comments from @stken2050 (and a few responses to them from others) that were explicitly continuing the confused argument about parentheses that I said just a few hours ago to not continue. I'm closing and locking this thread, and pursuing separate moderation actions, as I said I would if this behavior continued.

Ok.

that were explicitly continuing the confused argument about parentheses that I said just a few hours ago to not continue.

So individuals who are confused are not me but the members to promote this Hack proposal, and they believe they have a power to tell which argument is to be allowed to continue or not. In other words, this is an abuse of power or Censorship to justify their own proposal for their own interest to achieve the standardization of this false Hacked product.

Sure, I claimed to TC39, and I've got a mail from an individual on behalf of TC39 Chairs & CoC Committee:
https://gist.github.com/stken2050/5eff7d2a792cd2b9e773b09c64fd26da

Therefore, I understand TC39 has justified their abuse of power of censorship, and Banned me for 2 weeks.

EDIT (2021/9/28):
Now they added a false statement to the MDN page of Grouping operator ( ), for the purpose of justifying their own proposal that is based on the confusion of the concept: "evaluation order" derived from the dependency graph or structure is completely different concept of "time order" of executions.

Please confirm here:
Description confusing concepts clarified #9389


The point of view here has been shared in stack overflow question , and a third person confirmed the fact as the answer:

Grouping parentheses mean the same thing in Haskell as they do in high school mathematics. They group a sub-expression into a single term. This is also what they mean in Javascript and most other programming language,

A language needs other rules beyond just the grouping of sub-expressions to pin down evaluation order (if it wants to specify the order), whether it's strict or lazy. So since you need other rules to determine it anyway, it is best (in my opinion) to think of evaluation order as a totally separate concept than grouping. Mixing them up seems like a shortcut when you're learning high school mathematics, but it's just a handicap in more general settings.

Therefore, for the explanation of Grouping operator (parentheses) itself, the priority of the article should be to focus the functionality given as the meaning of "high school mathematics".

The wording of old version "operands" of "preserved" actively misleads readers to confuse the "high school mathematics" principle of the Grouping operator and the evaluation strategy of JavaScript runtime.

If anyone think such an example is required and to be included in this page, they need to explain thoroughly for readers to avoid the confusion of concepts between the mathematical aspect and evaluation strategy that the latter is essentially off-topic here.


Reference material

Does the functionality of Grouping operator () in JavaScript differ from Haskell or other programming languages?

https://stackoverflow.com/a/69386130/11316608


Long version

What is grouping-operator?

Grouping operator ( )

The grouping operator ( ) controls the precedence of evaluation in expressions.

The grouping operator consists of a pair of parentheses around an expression or sub-expression to override the normal operator precedence so that expressions with lower precedence can be evaluated before an expression with higher priority.

Grouping operator ( ) itself has the highest precedence in JavaScript.

Operator precedence

image

What is pipeline-operator?

In a general sense, pipeline-operator |> is a binary operator for function application that is equivalent to f(x)

f(x) == x |> f

Benefit of binary operator

Introducing a new binary-operator in JavaScript is nothing new.

In ES2016, exponentiation operator ** has been introduced.
Math.pow(2, 3) == 2 ** 3
Math.pow(Math.pow(2, 3), 5) == 2 ** 3 ** 5

As you can see, a binary operator significantly improve the readability of a code, especially for the nesting structure of f() notation,

Essentially, pipeline-operator is the same league of the exponentiation operator, and the benefit is also common.

g(f(x) == x |> f |> g

Expectation of the community

In fact, pipeline-operator in JS has been expected from the community.

#StateOfJS 2020: What do you feel is currently missing from JavaScript?

image

  • Static Typing
  • Pattern Matching
  • Pipe Operator
  • functions
  • Immutable Data Structure

It's reasonable to observe that the majority of the JS community has longed for more strictness of the language.

Especially for Static Typing:

image

Why Static Typing is so popular?

There is no native static type system in JavaScript, so currently, many use TypeScript instead.
So why do we like Type so much?

The general answer would be that we can avoid BUGs, in other words Type makes a code robust.

Why Type makes a code robust?

Because Type is mathematics.
I will explain briefly about Type since I think the understanding helps readers follow this discussion.

Types as Sets

Type == Sets

Type theory versus set theory

Alternately, we could change our terminology so that what we have been calling “types” are instead called “sets”.

Thus, words like “type” and “set” and “class” are really quite fungible. This sort of level-switch is especially important when we want to study the mathematics of type theory,

Types as Sets · An Introduction to Elm

In pursuit of this goal, I have found it helpful to understand the relationship between types and sets. It sounds like a stretch, but it really helps develop your mindset!

What is Sets?

Definition of Function

https://en.wikipedia.org/wiki/Function_(mathematics)#Definition

Intuitively, a function is a process that associates each element of a set X, to a single element of a set Y.

https://ncatlab.org/nlab/show/function

In a strict sense of the term, a function is a homomorphism f:S→Tf : S \to T of sets. We may also speak of a map or mapping, but those terms are used in other ways in other contexts.

So, in the definition of function, we need to define the sets of x and y
where y = f(x), or x |> y

A binary operator is a syntax-sugar of binary functions

Binary operation

As you can see in the picture,
x * y == f(x, y)

or should I edit this picture to

File:Pipeline operator.png

A binary operator is a syntax-sugar of binary functions.
We also need to type (== sets) x and y or f correctly as the request from the mathematical definition of function, and according to the overwhelming popularity of Static Typing in the survey, that is what people want. They needs more strictness of JavaScript for their robust codes.

For x |> f === f(x), essentially it's clearly typed:

x : JavaScript Objects
f : Function

Then, since this is f(x), the type(==sets) of x should be defined along with the definition of f.

This is what people want.

Hack-style reached Stage-2

Recently, I have noticed JS pipeline-operator has reached TC-39 Stage-2, so I have examined:
tc39/proposal-pipeline-operator


Pipe Operator (|>) for JavaScript

Why the Hack pipe operator

There were two competing proposals for the pipe operator: Hack pipes and F# pipes. (Before that, there was a third proposal for a “smart mix” of the first two proposals, but it has been withdrawn, since its syntax is strictly a superset of one of the proposals’.)

The two pipe proposals just differ slightly on what the “magic” is, when we spell our code when using |>.

This proposal: Hack pipes

In the Hack language’s pipe syntax, the righthand side of the pipe is an expression containing a special placeholder, which is evaluated with the placeholder bound to the result of evaluating the lefthand side's expression. That is, we write value |> one(^) |> two(^) |> three(^) to pipe value through the three functions.

Pro: The righthand side can be any expression, and the placeholder can go anywhere any normal variable identifier could go, so we can pipe to any code we want without any special rules:

  • value |> foo(^) for unary function calls,
  • value |> foo(1, ^) for n-ary function calls,
  • value |> ^.foo() for method calls,
  • value |> ^ + 1 for arithmetic,
  • value |> [^, 0] for array literals,
  • value |> {foo: ^} for object literals,
  • value |> `${^}` for template literals,
  • value |> new Foo(^) for constructing objects,
  • value |> await ^ for awaiting promises,
  • value |> (yield ^) for yielding generator values,
  • value |> import(^) for calling function-like keywords,
  • etc.

What??
I had a hope for value |> f is still valid, but syntax-error.

The type of right hand side of |> is no longer function but something unknown.

Reactions

I investigated the issue of this proposal.

In fact, #205: Hack is dead. Long live F#. has 199 comments and now closed by moderator.
https://github.com/tc39/proposal-pipeline-operator/issues/205#issuecomment-918717394

This is a huge thread and reading there, I've watched the issue was closed in real-time.

Here are few comments I've found around threads:

#205 (comment) @jderochervlk

The future of functional programming is Hack pipe

Nope. It isn't. If this is truly what became added to the language I would continue to use Ramda's pipe, which is a shame because I would really love to remove some overhead of installing a package in order to do something so simple.

#205 (comment) @samhh

I'm thinking ahead. JavaScript won't cease to exist once Hack reaches stage 4.

#215 (comment) @arendjr

I would rather have the language remain without any pipe operator than to have to deal with Hack in the future.

I too would like to thank @js-choi and the other contributors for all their effort, but I believe the current direction to be misguided to the detriment of not just the FP community, but the JS community at large.

#225 (comment) @voronoipotato

The argument against hack pipes is that we believe they are a hazard (sometimes for different reasons, but the conclusion is the same). Most of us in this thread and I suspect in the wild, would rather have no pipes, than hack pipes.

#225 (comment) @SRachamim

PFA proposal is a universal solution to those who worries about the arrow function noise on all three cases: map, then and pipe. If PFA is not ready, and we don't want minimal/F# without PFA, then let's wait, instead of introducing an irreversible Hack pipes.

#225 (comment) @SRachamim

And as I said, if PFA is stuck, then it's not a good reason to introduce Hack. We should either wait, or avoid pipe at all (or introduce minimal/f# style anyway).

#225 (comment) @Lokua

Also, as a writer whose job is to communicate meaning, hack style removes my ability to provide descriptive names, which is basically the universal first step of writing readable code. I'm honestly shocked at this proposal. Imagine if you started at a new company and they enforced that all unary functions you write regardless of context had to name their single argument x (or god forbid, ^ :trollface:). That's how I feel and I'm not exaggerating - this proposal scares me because it's going to lead to code that is uglier, harder to read, and harder to refactor/abstract later on!

Edit: I too would rather see no pipe than this and am admittedly biased as I've always thought a pipe operator (or pretty much any more new syntax) is a bad idea. But if we are going to have it, I'd rather see it implemented in a way that improves readability, not (IMHO) hinders it.

I've found they claim it's far better not to have any pipe than to have hack-style because they evaluated the one harmful to JavaScript.

This is a comment by RxJS auhthor
https://github.com/tc39/proposal-pipeline-operator/issues/228#issuecomment-925465598

I've been told several influential members want to discourage point-free programming. Therefore, anything that might help functional programming libraries is unlikely to pass the TC39. It is what it is. It's the hack proposal or nothing. IMO, the hack proposal isn't useful enough to justify the additional syntax. But I'm not on the committee

Please don't @ me into these threads. I've said my piece. Pretty thoroughly. I wasn't really heard. And I lost a friend over it. I simply just don't care what happens with this anymore. If it passes, great. Unfortunately, I don't really have any use for the proposed pipeline operator. But I'm hopeful for other features someday that I will have a use for.

History of the proposal

I think it's fair to share the history posted by a project member @js-choi

Brief history of the JavaScript pipe operator

My study on Hack

In fact, I had to study the Hack-style-pipeline-operator.
I had no idea what this is.
For the minimal/F# proposal, it's merely x |> f === f(x) , so simple, no requirement to study.

I had joined issues, and also I've opened a couple of issues by myself, and actually lots of text here is copy&paste of my own comment there.

Pipeline-operator in a mathematical sense

Before I discuss Hack pipe, I share my knowledge on the pipeline-operator in a mathematical sense that is
x |> f === f(x)

Associative property and Monoid

Addition

https://en.wikipedia.org/wiki/Addition#General_theory

General theory

The general theory of abstract algebra allows an "addition" operation to be any associative and commutative operation on a set. Basic algebraic structures with such an addition operation include commutative monoids and abelian groups.

Here the important property is associative

(1 + 2) + 3 = 
1 + 2 + 3 = 
1 + (2 + 3)
Enter fullscreen mode Exit fullscreen mode

JS String is also has associative property, and they are called Monoid in algebra.

In abstract algebra, a branch of mathematics, a monoid is a set equipped with an associative binary operation and an identity element.

Associativity

For all a, b and c in S, the equation (a • b) • c = a • (b • c) holds.

In computer science and computer programming, the set of strings built from a given set of characters is a free monoid.

What does this mean?

"Hello" + " " + "operator" ==
"Hello " + "operator" ==
"Hello" + " operator" ==
"Hello operator"
Enter fullscreen mode Exit fullscreen mode

Strings and + the binary operator in JS is a Monoid, and as you know this String operator is very easy to use and robust.

Monoid or associative property has rock solid structure because it's hardly broken.

Imagine LEGO block, that is also Monoid, the order to construct block does not effect the result:

(A + B) + C = 
A + B + C = 
A + (B + C)
Enter fullscreen mode Exit fullscreen mode

Any order to construction of the combination of LEGO bock reaches the identical result. So in software development if the entity has Monoid property, we can treat it as if LEGO block. That's the virtue of Monoid. Just its like LEGO, wonderful stuff.

Without the associative property, we will experience Combinatorial explosion.

In mathematics, a combinatorial explosion is the rapid growth of the complexity of a problem due to how the combinatorics of the problem is affected by the input, constraints, and bounds of the problem.

The history of software development is the war against complexity.

In fact, Associativity is one of the most important concept in programming and Associativity is the key to avoid the software complexity that is the fundamental reason of BUGs . In other words, as long as we are very carful to keep things with associative property, we safely can avoid complexity and obtain a bug-free code.

So Associativity is important, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation
image

They care a lot.

Function composition as a Monoid

https://en.wikipedia.org/wiki/Monoid

For example, the functions from a set into itself form a monoid with respect to function composition.

I will explain this.

One of "the functions from a set into itself" is addition.

// function from a set into itself
const f = a => a + 1; // f: number -> number 
const g = a => a + 2; // g; number -> number 
Enter fullscreen mode Exit fullscreen mode

image
image

image

image

  • please note I use the composition operator for g(f(x)) not g.f but f.g
a |> f |> g   ===
a |> (f . g) 
Enter fullscreen mode Exit fullscreen mode

This is the simple structure of
Function application (pipeline-operator)
Function composition (composition-operator).

They are the both side of the same coin.

image

(f.g).h === f.g.h === f.(g.h)

Therefore, function composition is Monoid.

a |> f |> g |> h ===
a |> f |> (g . h) ===
a |> f . (g . h) ===
a |> (f . g) |> h ===
a |> (f . g) . h ===
a |> (f . g . h) 
Enter fullscreen mode Exit fullscreen mode

where f.g is the composition of f and g

(in traditional math style is g.f regarding g(f) but I don't use this style )

This is the whole picture you should know, and as you can see:

function application |> is not associative and not Monoid.

a |> f |> g   !=
a |> (f |> g) 
Enter fullscreen mode Exit fullscreen mode

The (f |> g) does not make sense and the Type==Sets is violated in terms of x |> f.

However, this is what the Hack style is doing.

const f = a => a * 2;
const g = a => a + 1;

1 |> f(^) |> g(^);    //3
1 |> (f(^) |> g(^));  //3
Enter fullscreen mode Exit fullscreen mode

and (f(^) |> g(^)) is also evaluated as 3,
with the higher priority of general rule of Mathematics or grouping-operator ()

as a result

1 |> 3 == 3

This does not make sense at all, and the fundamental reason is they simply violates the rule of mathematics.

Monad

Pipeline-operator |> and function application does not have associative property in Monoid layer, but the form

a |> f |> g   ===
a |> (f . g) 
Enter fullscreen mode Exit fullscreen mode

is also called Associativity in Monad layer.

https://wiki.haskell.org/Monad_laws

Monad laws
image

For your convenience, I would rewrite to

Associativity: (m |> g ) |> h === m |> (x => g(x) |> h)
or
Associativity: (m |> g ) |> h === m |> (x => x |> g |> h)
as (x => x |> g |> h) is the function composition of g and h
Associativity: (m |> g ) |> h === m |> (g . h)

For left-right identity,

image

with identify function: id= x => x
For every Function: f: A ->B
If this forms, the algebraic structure is called Monad.
Note: this corresponds to Monoid has associativity and identify.

  • Function Application |> Monad
  • Function Composition . Monoid also Monad (obvious, but prove by yourself)

So mathematically, |> is Monad, not Monoid.

FYI,
Array.prototype.flatMap() introduced in ES2019
https://github.com/tc39/proposal-flatMap

https://github.com/tc39/proposal-flatMap/issues/10

This proposal essentially gives native Arrays the methods they need to work as Monads (without having to extend the prototype).

They add flatMap on purpose as Monad on top of the Map.

Pipeline-operator |> in the mathematical sense of function application is natively Monad that behaves pretty well with the rock-solid structure that majorities of programmers in JavaScript desired for, and now, Hack pipe destroys that. No respect to Mathematics.

Math structure of Hack pipe

F# pipe is simply mathematical function application, it behaves like Monad, so it's natural it does not behave Associative in Monoid.

Hack pipe, on the other hand, it behave like Associative and Monoid,

a |> f(^) |> g(^)   == 
a |> (f(^) |> g(^)) 
Enter fullscreen mode Exit fullscreen mode

but this is not a monoid.
Something unknown to leads:
1 |> 3 == 3
This is something completely different from Function application in mathematical sense.

This one breaks the Algebraic structure that I've explained so far.
Function application is NOT Monoid and should not behave like Monoid.
Function composition is Monoid but with Hack pipe, there is no such a concept anymore because it has broken the mathematics.

With pipeline-operator in the algebraic sense,

a |> f |> g   ===
a |> (f . g) 
Enter fullscreen mode Exit fullscreen mode

This is Associative in Monad layer with Function composition that itself is Associative and Monoid/Monad in both layers.

Type of Hack pipe

I'll give it another shot.

As a type,

A |> F |> F |> F is replaced to
A * F * F * F
===
A * (F + F + F)
where
A * F is function application, and
F + F is function composition.

Hack on the other hand,
They claim the simplicity:
A * F * F * F
===
A * (F * F * F)
We no longer understand what the (F * F * F) is.
In fact, they say:
(F * F * F) itself is a syntax-error.
Sure it should be because it doesn't make sense, and
(F * F * F) is refused to be evaluated because they ignore the rule of grouping-operator.
In other words, they overrides a rule of the operator that has the highest precedence priority in JavaScript, that I would call hijack.
(F * F * F) is not a function composition like in F# or math pipe, nor anything understandable, so Type is ?
A * (?)
and (?) appears to be the evaluated value of the whole expression (such as 3):
? == A * (?)
therefore
A * (A * (A * (A * (A * (A * (A * (..?..)))))))
Some structure of infinite recursion.
That is the Type of the Hack pipe. Scary.

Hijacking grouping-operator ()

As you can see,
image

Internally, technically, the Hack |> refuses to evaluate (f(%) |> g(%)) first ignoring the rule of

Grouping operator ( )

The grouping operator consists of a pair of parentheses around an expression or sub-expression to override the normal operator precedence so that expressions with lower precedence can be evaluated before an expression with higher priority.

Grouping operator ( ) itself has the highest Operator precedence in JavaScript.

Then I hear the counter explanation to justify Hack pipe anomaly:
https://github.com/tc39/proposal-pipeline-operator/issues/227#issuecomment-926317660

It’s just the old left-to-right evaluation rules. There aren’t any special grouping rules here. Parentheses change the grouping, but they never have changed execution order from anything other than left to right.

"old left-to-right evaluation rules" that means eager evaluation strategy of JavaScript follows the rule in the sense of both Mathematics and grouping-operator ().

Eager evaluation does not conflict to evaluation order.

The evaluation order arises from Dependency graph

In mathematics, computer science and digital electronics, a dependency graph is a directed graph representing dependencies of several objects towards each other. It is possible to derive an evaluation order or the absence of an evaluation order that respects the given dependencies from the dependency graph.

and () defines the dependency graph and the structure of the code.

Remember dependency graph or structure is completely different concept of time order.

Here, in the terms of "evaluation order", "before", "after" we discuss Not time order of the evaluation/excision, but dependency structure of the code, which unfortunately it seems everyone in the Hack pipe proposal team share the confusion of the concept.
As we've seen, the Hack pipe refuses to follow the evaluation order of dependency structure and I would call this Hijacking grouping-operator ().

I did explain to them, but they did not hear, then deleted my explanation. That is why I made a post here.

Current TC39 proposal Hack Pipeline Operator |> has severe problems including the process of the staging, and the entire community of JavaScript will suffer.

EDIT (2021/9/28):
Now they added a false statement to the MDN page of Grouping operator ( ), for the purpose of justifying their own proposal that is based on the confusion of the concept: "evaluation order" derived from the dependency graph or structure is completely different concept of "time order" of executions.

I made Issues:
Issue with "Grouping operator ( )": (invalid statements added) #9306

Implicitly misleading into confusion of concepts: "Grouping operator ( )" #9317

Update Grouping operator ( ) #9325


Please confirm here:
Description confusing concepts clarified #9389


The point of view here has been shared in stack overflow question , and a third person confirmed the fact as the answer:

Grouping parentheses mean the same thing in Haskell as they do in high school mathematics. They group a sub-expression into a single term. This is also what they mean in Javascript and most other programming language,

A language needs other rules beyond just the grouping of sub-expressions to pin down evaluation order (if it wants to specify the order), whether it's strict or lazy. So since you need other rules to determine it anyway, it is best (in my opinion) to think of evaluation order as a totally separate concept than grouping. Mixing them up seems like a shortcut when you're learning high school mathematics, but it's just a handicap in more general settings.

Therefore, for the explanation of Grouping operator (parentheses) itself, the priority of the article should be to focus the functionality given as the meaning of "high school mathematics".

The wording of old version "operands" of "preserved" actively misleads readers to confuse the "high school mathematics" principle of the Grouping operator and the evaluation strategy of JavaScript runtime.

If anyone think such an example is required and to be included in this page, they need to explain thoroughly for readers to avoid the confusion of concepts between the mathematical aspect and evaluation strategy that the latter is essentially off-topic here.


Also, my next article:

Does the functionality of Grouping operator () in JavaScript differ from Haskell or other programming languages?

Discussion (3)

Collapse
stken2050 profile image
Ken Okabe Author • Edited

StackOverflow Question
Does the functionality of Grouping operator () in JavaScript differ from Haskell or other programming languages?
stackoverflow.com/questions/693846...

Check out one of the detailed great answer:
stackoverflow.com/a/69386130/11316608

or you can join the Q&A.

Collapse
stken2050 profile image
Ken Okabe Author • Edited

StackOverflow Question
Does the Hack-style pipe operator |> take precedence over grouping operator ( ) in order of operations in JavaScript? [closed]
stackoverflow.com/questions/694125...

5 Answers
3 from 2 members
2 from myself to respond them.

Closed reason may be I said at the bottom of my answer:

Observing such a situation, the question arises:
Does the Hack-style pipe operator |> take precedence over grouping operator ( ) in order of operations in JavaScript?

What do you think?

In fact, you say I have a confusion, and the fact is not. I'm not confused at all, and I think you are confused.
To me, if the creation of the system as the syntactic sugar is not compatible with the general usage of the grouping ( ), it's a failure work, and the community should not accept such a thing.

stackoverflow.com/a/69431334/10638454

Collapse
t7yang profile image
t7yang

Great explanation 👍
I think the champions are now biased and opinionated. Hopefully this is my personally wrong impression 🤔