proposal-pipeline-operator: Inconsistency of Type of the operator
README.md of the current draft specification:
For F# proposal:
value |> x=> x.foo()for method calls,value |> x=> x + 1for arithmetic,value |> x=> [x, 0]for array literals,value |> x=> {foo: x}for object literals,value |> x=> `${x}`for template literals,value |> x=> new Foo(x)for constructing objects,value |> x=> import(x)for calling function-like keywords,- etc.
The type is extremely simple and clear in terms of
value |> f:
where
value: Object, Number, Array, Function or any others.
f: Function (lambda expression)
In other words, the types == sets of the binary operation is well defined which fits to TypeScript eco.
In mathematics, a binary operation or dyadic operation is a calculation that combines two elements (called operands) to produce another element. More formally, a binary operation is an operation of arity two.
More specifically, a binary operation on a set is an operation whose two domains and the codomain are the same set.
Such binary operations may be called simply binary functions. Binary operations are the keystone of most algebraic structures that are studied in algebra, in particular in semigroups, monoids, groups, rings, fields, and vector spaces.
Binary operator/operation is identical to binary function:

x * y ===
operator(x, y) ===
operator(x)(y)
or should we rewrite to:
a |> f ===
operator(a, f) ===
operator(a)(f)
In fact, the conversion from binary function to binary operator happened in JS.
Fundamentally, essentially, this proposal is to introduce a new binary operator to JS, which is the same league of exponentiation operator ** introduced In ES2016
Syntax
Math.pow(2, 3) == 2 ** 3
Math.pow(Math.pow(2, 3), 5) == 2 ** 3 ** 5
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Exponentiation
Then, in this binary operator, the type is clear: Number ** Number
In any case, the input types == sets of x, y, or a , f should be defined (as we do in TypeScript) as a request from definition of function, and for F# proposal, a |> f, essentially it’s clearly defined in principle.
For Hack proposal:
value |> foo(^)for unary function calls,value |> foo(1, ^)for n-ary function calls,value |> ^.foo()for method calls,value |> ^ + 1for 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.
value: Object, Number, Array, Function or any others.
The right side: ???
For instance,
value |> foo(1, ^)for n-ary function calls,
What is the type of foo(1, ^) ?
Function?
https://github.com/tc39/proposal-pipeline-operator/issues/223#issuecomment-922914884,
You gave this example:
value |> foo(1, ^) |> bar(2, ^)You can rewrite it as(value |> foo(1, ^)) |> bar(2, ^)or asvalue |> (foo(1, ^) |> bar(2, ^))using hack pipes, and it will yield the same result. Isn’t this the definition of associativity?
So, to be clear:
value |> foo(1, ^) |> bar(2, ^) ==
(value |> foo(1, ^)) |> bar(2, ^) ==
value |> (foo(1, ^) |> bar(2, ^))
and for your convenience, I can replace to:
Well, I could replace to… IF I am allowed to have the type of foo(1, ^) as Function
a |> f |> g ==
(a |> f) |> g ==
a |> (f |> g)
So, now, what is f |> g?
a |> f |> g ==a |> (f |> g) ??
Suddenly, the function application operator |> became to Function composition operator .?
The type does not match.
For F# proposal, the above is, of course:
a |> f |> g ==
(a |> f) |> g ==
a |> (f . g)
Associative in the form of Monad. https://github.com/tc39/proposal-pipeline-operator/issues/223#issuecomment-923324793
On the other hand, what is (f |> g) in the hack proposal?
What is the (foo(1, ^) |> bar(2, ^))? What is the type?
According to https://github.com/tc39/proposal-pipeline-operator/issues/223#issuecomment-923225188 ,
It’s not a function at run-time, it’s not even legal syntax on its own. But if you really stretch the meaning, it is a “function” of the topic variable in the grammar — it can only appear on the RHS of pipe operator, which applies the “function” to the LHS.
So, suppose
the type of (foo(1, ^) |> bar(2, ^)) is Function,
Function |> Function = Function
|> function application
now behaves like
. function composition.
Summary
With hack proposal pipe |>,
-
foo(1, ^)is not an expression with the type of Function as it appears because typing it as Function, as I’ve illustrated the operator function-application becomes function-composition. Impossible to type for this expression -
We will have an expression
(foo(1, ^) |> bar(2, ^))that is not even legal syntax on its own. Impossible to type for this expression.
value |> foo(1, ^)for n-ary function calls,
a |> ?? |> ?? == a |> (?? |> ??) == a |> ??
where ?? is something unknown that is impossible to type.
Now, probably the usage of TypeScript becomes useless even for “mainstream”.
|> has many contexts in terms of types, and something is impossible to type, and there is no way to tell in which context |> is used with full of ^ that also has context for each.
Finally, as a functional programmer who has used the pipe as f(x) = x |> f , since |> is strictly defined as the binary operator of between a value and function, it was easy to use and the code keeps robustness.
In fact, I appreciate feedback from TypeScript users with hack-pipe.
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 58 (15 by maintainers)
I am junior, and I will take up the courage to say a few words with my poor english.
After reading your writing, I understand hack pipe does not make sense in math, and I also can understand why ljharb says it make no difference. Because they admit that
?? |> ??can not exist by itself, so I guess TS does not need give a type for?? |> ???As far as I know, it seems they did not say
|>is function application operator or function composition operator ?For me, it is just a operator for pipe nested operations in linear form, and I learned hack pipe is not function application operator, and it is also not function composition operator(because can not exist by itself).
And, it seems we can continue to try to add a real function application operator with a symbol
|>>.So, with all these information, what terrible things may hack pipe bring? Maybe it is the question which ljharb really care about?
At last, I personally can accept that
????in below can not exist by itself and don’t have a type. For me, I just want an operator which can let me do a series of things with linear form.As has been explained several times, the answer is
3. Refer to https://github.com/tc39/proposal-pipeline-operator/issues/227#issuecomment-926136875.So, let’s reason about
1 |> (f(^) |> g(^)).1is evaluated1is pushed onto the^stack:[1]f(^)is evaluated,^references the last item, sof(1), the result is22is pushed onto the stack:[1, 2]g(^)is evaluated,^references last item, sog(2), the result is3.3^stack:[1]3^stack:[]3“But wait!” you ask, “
(f(^) |> g(^))has to evaluate first! Gotcha!”. It did evaluate first, and that’s the part you don’t seem to understand. There’s a difference between nested evaluations and sequential evaluations.In the case of
(1 |> f(^)) |> g(^), what happens? You have sequential evaluations of the two different pipes, not a nested evaluation.1is evaluated1is pushed onto the^stack:[1]f(^)is evaluated,^references the last item, sof(1), the result is22^stack:[]1 |> f(^)is finished evaluating and its result is2. We’re evaluating2 |> g(^). This is the second evaluation of the pipe, we’re at a brand new expression.)2is evaluated2is pushed onto the^stack:[2]g(^)is evaluated,^references last item, sog(2), the result is3.3^stack:[]3So, now of course you’re gonna be pedantic and demand I answer what
1 |> f(^) |> g(^)does. Well, it’s the exact same as the first case. Why? Because the spec is defined that way. Instead of relentlessly quoting your math ideas in this thread, please read the spec.And if you’re not willing to do so, then you’ll need to trust us when we give you authoritative answers. Because we can read the spec.
I have provided a concrete full code. https://github.com/tc39/proposal-pipeline-operator/issues/227#issuecomment-926237799 https://github.com/tc39/proposal-pipeline-operator/issues/227#issuecomment-926248041
and this is the third time:
Now we made
(f(^) |> g(^))to be evaluated before other expressions with higher priority. What is the evaluated value? whereGrouping operator
( )Please answer.
https://github.com/tc39/proposal-pipeline-operator/issues/229#issuecomment-926308352 @tabatkins
So now, a member admitted that the current proposal override the Grouping operator
( )with Hack|>The new operator with the highest precedence ever in JavaScript. I wonder how the JS community react… because basically breaking the
()functionality is against mathematics, too.@stken2050: At this point, 3 people have tried to give you the correct answer. Let’s walk through this slowly:
This logs:
"two""three""four"14Notice 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 is10. That’s because the operands are evaluated left to right, and this doesn’t change. What changed was the evaluation of the operators. I’m certain, because you’re so good with math, you can reason why2 * (3 + 4)is different than(2 * 3) + 4.That’s not what I said.
Anyway, your copy & pasting walls of text over and over doesn’t help with anything. Your question what is the value of
1 |> (f(^) |> g(^));has been answered long before, I don’t know why you’re reposting it. It’s 3.It seems to me you simply refuse to acknowledge that
(f(^) |> g(^))does not have a value, because it’s not a valid Expression, it’s only valid as PipeBody on the right-hand-side of|>and needs to be fed topic to be evaluated.It’s not math, it’s JavaScript. The core baseline we must always start with is existing JavaScript semantics. Anything else is only relevant when it doesn’t conflict with that.
It doesn’t. Parentheses work as always, but thanks to associativity, rearranging them doesn’t change the result. In that sense,
|>is a well-behaved operator unlike say+, which does not guarantee thata + (b + c) === (a + b) + c.@xxleyi Thanks for your courage to speak up.
Correct, it does not make sense in mathematics if seriously investigated that is what I’ve done last week, and I firmly believe such a thing should not be accepted for the very basic function application operator because I think this one will severely mess up the Algebra structure of the entire JS eco.
Secondly, currently, it appears either of the below: a) The advocators of Hack never accept the fact Hack pipe breaks very basic laws in Algebra. b) Once accepted, the advocators insist it’s not the “goal” of this proposal.
For a) I’ve encountered this problem a lot until now, however, eventually, the fact will be revealed to the public in any way because this is math. It’s relatively easy to prove, and this one is not politics.
For b) some political matter to be discussed, however, I don’t think the majority of JS will happily accept a new binary operator in principle that has algebraic consistency but actually inconsistent.
In fact, according to #StateOfJS 2020: What do you feel is currently missing from JavaScript? https://2020.stateofjs.com/en-US/opinions/missing_from_js
It’s obvious that majorities have longed for more strictness of JavaScript
- Static Typing
the thing super majorities feel currently missing from JavaScript.
Therefore, I expect the messed-up-typed-hack-pipe will not be accepted by them.
According to the research above, such an opinion is against the trend of the needs in JS, or any other languages. The majority needs more strict typed environment that helps their coding to be robust.
Surly, TypeScript community will easily discover for this hack
|>, things are impossible to type. It’s as a matter of course because the designers have not paid any attention to the algebraic strictness, they simply ignored it because it’s not the goal of this proposal. Pity.As reading
readme.md, I have a strong impression that the proposal is camouflaged as if the difference is very little but the fact is the difference is significant.It’s obvious when the majority of JavaScript long for
which must be the function application, of course, but actually, this one is not.
The problem is in read-me or in other places, as far as I know, they never clarified this aspect to the public broadly, which is why I have not noticed and I think the majorities still believe what’s coming is F#-style pipe.
https://github.com/tc39/proposal-pipeline-operator/issues/225#issuecomment-924479581 A couple of people including me start thinking that hack-pipe is so harmful that if it’s the default route as they claim, it’s far better not to have any. I feel very sorry not to have F# or minimal style, but it’s better than JS will be broken forever. Then, I also hope we have operator overloading, then totally no problem.
operator is evaluated with operands. As I said earlier, binary operator is a syntax sugar of binary function.
In binary operation, the function application is
operator(LHS, RHS)Now you are saying the evaluation of function is only
operatorandxis nothing to do with it.Please stop inventing your own math rule.
Binary operator/operation is identical to binary function:
or should we rewrite to:
In fact, the conversion from binary function to binary operator happened in JS.
Fundamentally, essentially, this proposal is to introduce a new binary operator to JS, which is the same league of exponentiation operator
**introduced In ES2016 SyntaxMath.pow(2, 3)==2 ** 3Math.pow(Math.pow(2, 3), 5)==2 ** 3 ** 5https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/ExponentiationThen, in this binary operator, the type is clear:
Number ** NumberPlease stop inventing your own math rule.
You seem to not understand what you really are doing.
Do you guys override the Grouping operator
( )with Hack|>?That is not how JS works. In
a(1) + (b(2) + c(3)), the function calls are evaluated in the order they’re written. Same with(a(1) + b(2)) + c(3). Same witha(1) ** (b(2) ** c(3))and witha(1) |> (b(^) |> c(^)). The first thing evaluated will always bea(1)no matter where you place parentheses.OT: to clarify what I meant before with
+not being associative, that’s due to IEEE floating-point arithmetic.1e16 + (1 + 1) !== (1e16 + 1) + 1@stken2050 but that’s not what the grouping operator does in every case, so I don’t think your expectation holds. Parens don’t change left-to-right evaluation.
It’d be 3 in both cases. “before the other expression with higher priority” is not what grouping with parens does, and in this case, I believe pipeline is both left and right associative, so parens have no effect on the result - which is true in
a * b * cwhether you group the first two or last two operands.what part of “controls the precedence” is a law that’s been broken?
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.
We can follow the spec (https://tc39.es/proposal-pipeline-operator) step by step to see how
value |> (foo(1, ^) |> bar(2, ^))is evaluated:valueEvaluateWithTopics(https://tc39.es/proposal-pipeline-operator/#sec-evaluatewithtopics) on(foo(1, ^) |> bar(2, ^)), which:topicValuesspec variable, which is a list containing a single value (cc @js-choi this doesn’t need to be a list, it can be simplified): it contains the result of evaluatingvaluefoo(1, ^) |> bar(2, ^)When evaluating
foo(1, ^) |> bar(2, ^), it: (https://tc39.es/proposal-pipeline-operator/#sec-pipe-operator-runtime-semantics-evaluation, again)foo(1, ^)foo, then1, and then^. When evaluating^callsGetPrimaryTopicValue(https://tc39.es/proposal-pipeline-operator/#sec-topic-references-runtime-semantics-evaluation), which:^is the first (and only) topic value associated to that environment, so it was the result we got previously while evaluatingvalue.EvaluateWithTopics(https://tc39.es/proposal-pipeline-operator/#sec-evaluatewithtopics) onbar(2, ^), creating a new environment whose^binding is the result got previously while evaluatingfoo(1, ^).