Concern on Hack pipes on algebraic structure in JavaScript
ken-okabe opened this issue Β· 21 comments
Since a moderator marked my post as off-topic even though I firmly believe my post exactly corresponds to the issue where I thought myself invited to because I was told so, here I start my own new issue and this first post is exactly the on-topic.
First of all, I appreciate @js-choi 's hard work (must be, I guess) for Brief history of the JavaScript pipe operator
that I think the information listed is extremely valuable for everyone who concerts the matter.
Here I would like to express my concern on Hack pipes on algebraic structure in JavaScript.
Reading through his work describing the history, I feel very sorry for this proposal, especially @rbuckton's contribution.
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
Replacements an expression of OOP style to a binary operator in an algebraic sense make the code concise and readable, easier to grasp the math structure which leads us to avoid mistakes then the code becomes robust. This is a very powerful approach, and perhaps some people observe this manner as FP code. Haskell codes are full of binary operators, and Haskellers basically do just Math/algebra in their code.
A replacement of a syntax
f(x)
== x |> f
g(f(x))
== x |> f |> g
is the same league of **
binary operator.
Replacement and replaceable or equivalent itself is a very powerful concept. Why? Because equivalency reduces complexity.
The history of software development is a war against complexity.
If we are very careful to treat programming entities kept equal all the time, we safely can avoid the problem of combinatorial explosion.
https://en.wikipedia.org/wiki/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.
So, to keep entities of programming equal or to be replaceable equally all the time is an extremely important concept, and although I don't like to use this term, many call this advantage referential transparency.
https://en.wikipedia.org/wiki/Referential_transparency
Referential transparency and referential opacity are properties of parts of computer programs. An expression is called referentially transparent if it can be replaced with its corresponding value (and vice-versa) without changing the program's behavior. This requires that the expression be pure, that is to say, the expression value must be the same for the same inputs and its evaluation must have no side effects. An expression that is not referentially transparent is called referentially opaque.
Obtaining a concise binary operator contributes to reduce the complexity of our codes.
Math.pow(Math.pow(2, 3), 5)
== 2 ** 3 ** 5
g(f(x))
== x |> f |> g
Of course, the above code is referentially transparent. They are Just math equations, and since things are always equal and replaceable, we are guaranteed to be safe from the complexity of combinatorial explosion.
On the other hand in hack "operator", I mean mathematically operator is "pure" in concept but this one is not "pure", we are not safe any longer.
g(f(x))
!= x |> f |> g
g(f(x))
!= x |> f(^) |> g(^)
The hack "operator" spoils equivalency and referential transparency or introduces a side effect.
The side effect is to introduce a new context variable ^
.
This fact has been apprehended with certainty here recently:
Question about ^ when a "child pipeline" is present. #208
#208 (comment) @mAAdhaTTah
It seems like the presence of a
|>
in a child function would give new meaning to the^
on its RHS until the end of the function's context?This is correct: the
|>
operator introduces a new expression scope, so the(^)
on line 6 refers to what you expect it to.
The reason programmers avoid side effects is this is the factor of complicity and fundamentally this is mathematically inconsistent.
https://en.wikipedia.org/wiki/Referential_transparency
In mathematics all function applications are referentially transparent, by the definition of what constitutes a mathematical function. However, this is not always the case in programming, where the terms procedure and method are used to avoid misleading connotations. In functional programming, only referentially transparent functions are considered. Some programming languages provide means to guarantee referential transparency. Some functional programming languages enforce referential transparency for all functions.
Losing referential transparency, a real-world programmer who has used hack-style-operator for a certain period of time actually has been suffered.
As a dev who used reactive extensions a lot in my career (whether it's rxjs or rx.net), I've noticed the same problem arise in nearly all the teams that try to use it. They all end up leaking state from the pipeline. In Rx pipelines, this leads to race-conditions and horrible issues with code readability on top of making the code completely impossible to reuse. In the end, there is no way to prevent this, and the pipeline operator style will not influence how people uses Rx that much. The problem isn't with Rx though. It is within limitations of the pipeline mechanism that it uses. As it is heavily based on lambdas, most of the code in Rx captures the scope outside the pipeline and thus depends entirely on the developer to use that captured scope correctly. Any language feature which would discourage this behavior would have saved me a very large amount of wasted time debugging those issues.
Do we really want to choose a pipeline operator that also has this problem by default? With hack style, you can easily extract the state of the pipeline at any point in an external variable. Aka, this code seems valid:
value |> (value = ^) |> log(^)
. By requiring expressions for every step of the pipeline, you end up with a clear path to extract the pipeline state. You might say "who in their right mind would write code like this?" and yet this is the kind of stuff I have seen done in Rx multiple times.
. While F# style doesn't prevent this, it at least discourage it by taxing it heavily when compared to calling an FP friendly function:
value |> x => (value = x) |> log
. This style doesn't expect an expression with access to the pipeline' state by default. You have to declare it. You have to explicitly state that you are up to no good in this part of the pipeline and this makes me instinctively pay more attention to what you are trying to accomplish. This form actively helps preventing mistakes. Arguably, the minimal style might even be better in this scenario since, I believe, those scenarios would be much more taxed.
new this
?
microsoft/TypeScript#43617 (comment) @weswigham
Personal opinion: The hack-style pipeline operator is bad because it introduces a new context variable, like
this
, in the form of#
. These don't nest in intuitive ways. In addition, the operator doesn't actually save much space for many usages. You could rewrite....
this
is a bad legacy of OOP and very hard to control. To be fair, as @js-choi suggested, this
is dynamic and #
or ^
is lexical, however, both of them have a fixed name and it appears in any layers of deepness.
The functional pipeline operator, on the other hand, exists to deliver a point-free style that otherwise is impossible in JS - while some people may hate this style, that is what the true functional pipeline operator truly enables.
It's not my intention to make the topic broader and there is an active-sub topic Tacit programming / point-free style and pipes #206
however, the fundamental reason F# style(true functional pipeline operator) automatically fits on point-free style is simply
g(f(x)) == x |> f |> g
They hold concise mathematical equality and did not mess up the algebraic structure. then referential transparency.
On the other hand, with hack-style, obviously, we encounter problems to reach that.
Why? because losing equality and the math is messed up.
g(f(x)) != x |> f |> g
g(f(x)) != x |> f(^) |> g(^)
JavaScript is originally developed by Brendan Eich, a functional programmer who had been Scheme enthusiast. That's why JavaScript has a core feature of functional language such as functions are first-class objects.
Now, we have a pipeline "operator" that should have been considered as a very basic binary operator of function application that is a very basic algebra operation in the mathematical sense but actually polluted by a side-effect.
Concern on Hack pipes on algebraic structure in JavaScript
With Hack pipes, we've lost JavaScript as a functional language. Perhaps that's why many are upset. It's not a matter of "interoperability", and "silent majorities" will care about this matter a lot.
To be fair, I already received a feedback
Introducing a context variable is not a "side effect." A side effect is a modification outside the local environment. Hack pipe introduces the value into the local environment the same way a parameter to a function does, but it does not modify anything outside of it. The Hack pipe is thus side-effect free.
What's more, it doesn't break referential transparency either. If you took
x |> f(^)
and replaced it withf(x)
(or the value returned byf(x)
), the result is the same, which is what referential transparency is; namely, the ability to replace a function with the result of that function without changing the behavior of the application. This is closely related to it being side-effect free. Hack pipe doesn't lose referential transparency.Hack doesn't break math, nor does it lose JavaScript as a functional language.
My answer is:
Introducing a context variable is not a "side effect."
I disagree
A side effect is a modification outside the local environment.
Only half correct.
Extracting the context, state from outside is also "side-effect".
Providing a connection or opening an entry-hole to the outside is also a modification. From the outside, we can see the open hole and freely enter.
Having said that the definition of the term is not an essence here.
What does matter is we can extract internal variables with the operation of hack-pipe, and that never happens in math function that is referential transparent in the definition.
They say F# can do the same, and in fact not.
The illustration of their F# piple code is actually a programmer defines new lambda function, then operates function application.
We decalair a new function f: a => a * 2
then 5
is functionally applied or pipelined,
5 |> a => a * 2 |> console.log
5 |> f |> console.log
There are no context variables to leak outside. The variable of a
is strictly hidden in the function f
.
Please note: the above code essentially corresponds to the comment:
While F# style doesn't prevent this, it at least discourage it by taxing it heavily when compared to calling an FP friendly function:
value |> x => (value = x) |> log
. This style doesn't expect an expression with access to the pipeline' state by default. You have to declare it.
On the other hand, hack pipe leaks the context variables ^
as illustrated in my previous (the first)post in this issue.
Hack pipeline operator no longer satisfies Associativity law.
In this comment, I will explain how Hack pipeline operator has broken Associativity law in algebra.
In mathematics, the associative property is a property of some binary operations, which means that rearranging the parentheses in an expression will not change the result.
In my first post, I commented:
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
and in the page, there is a section mentioning Associativity
So, you may be wondering why they care Associativity in programming.
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.
2 ** 3 ** 2 // 512
2 ** (3 ** 2) // 512
(2 ** 3) ** 2 // 64
The exponentiation operator is right-associative:
a ** b ** c
is equal toa ** (b ** c)
.
and this is a very important information to remember for us because as long as we are within this rule, the code will not be broken, for instance we can extract or replace (b ** c) = x
with no problem.
However, we cannot extract or replace (a ** b) = y
part, in this case, the code will be broken.
The point is we had never had this kind of perspective of associativity in OOP or other codes:
Math.pow(2, Math.pow(3, 2))
== 2 ** 3 ** 2
== 2 ** (3 ** 2)
Further more, there are more robust algebraic structure: Monoid
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"
Strings and +
the binary operator in JS is a Monoid, and as you know this String operator is very easy to use and robust.
Of course, the plus operator
(1 + 2) + 3 == 1 + 2 + 3 == 1 + (2 + 3)
is also Monoid, and easy to use.
As I mentioned in my first post here,
Replacement and replaceable or equivalent itself is a very powerful concept. Why? Because equivalency reduces complexity.
The history of software development is a war against complexity.
If we are very careful to treat programming entities kept equal all the time, we safely can avoid the problem of combinatorial explosion.
https://en.wikipedia.org/wiki/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.
Therefore, when we operate Strings with +
operator, we don't have to care the order to compose another Strings. We don't have the problem of combinatorial explosion. Robust structure.
The Wikipedia page smartly mentions functions. Yes, function composition is a monoid.
For example, the functions from a set into itself form a monoid with respect to function composition.
- please note I use the composition operator for g(f(x)) not
g.f
butf.g
So,
a |> f |> g |> h ===
a |> f |> (g . h) ===
a |> f . (g . h) ===
a |> (f . g) |> h ===
a |> (f . g) . h ===
a |> (f . g . h)
The all combinations of functions are equal and robust like Strings operation.
Now, do you want to write this in with hack (^)
?
Absolutely not. I think the hack-style obviously arises from lack of respect of Mathematics.
Pipeline-operator |>
and function operator .
have associative property, and with identify function: id= x => x
,
For every Function: f: A ->B
So, Function composition .
is a Monoid.
At the same time, Function application (pipeline operator) |>
is a Monad.
I like to prove monad property with function-composition because I think it's elegant, but since Haskell Monad laws seems to be well known, I have a code to prove in both ways.
https://gist.github.com/stken2050/4b61a045c4f1c2fb6a23fc341efbe6cf
Hack pipe |>
value |> foo(^)
for unary function calls
satisfies Associativity law because ^
is simply a extra thing.
however,
value |> foo(1, ^)
for n-ary function calls or others
does not satisfy Associative law because the operator is no longer compatible with function composition.
value |> foo(1, ^) |> bar(2, ^)
There is no way to do the sane thing of the pure |>
and .
.
Remember math or functional of minimal-style, or F#-style pipeline operator always satisfies the Associative law, which does not apply to hack-style.
Pardon my naivete, but isn't it permitted with hack ?
a |> f(^) |> g(^) |> h(^) ===
a |> f(^) |> (g . h)(^) ===
a |> (f . (g . h))(^) ===
a |> (f . g)(^) |> h(^) ===
a |> ((f . g) . h)(^) ===
a |> (f . g . h)(^)
@fuunnx Thanks for your inquiry, and sure it is permitted, and I am very afraid of the possibility they will also mess function composition operator .
with (^)
.
There are infinite number of binary operators in theory, like monad operator might be assigned to >>=
and I wonder how we decide which binary operator should be messed up, which one be not.
In terms of breaking associativity, as I wrote in the final section, it's only for unary function calls, and n-ary function calls or others does not satisfy Associative law because the operator is no longer compatible with function composition.
Remember math or functional of minimal-style, or F#-style pipeline operator always satisfies the Associative law, which does not apply to hack-style and this is a huge problem.
You gave this example: value |> foo(1, ^) |> bar(2, ^)
You can rewrite it as (value |> foo(1, ^)) |> bar(2, ^)
or as value |> (foo(1, ^) |> bar(2, ^))
using hack pipes, and it will yield the same result. Isn't this the definition of associativity? The quote you cited from wikipedia is exactly about rearranging parentheses, and these are the two ways that this expression can be parenthesized.
On the other hand, F# |>
is not associative: (1 |> add2) |> times3
gives 9, 1 |> (add2 |> times3)
throws "NaN
is not a function" because it's trying to call the result of times3(add2)
(which is NaN, since in JS you cannot multiply a function).
For
(1 |> add2) |> times3
We simply rewrite
1 |> (add2 . times3)
Where .
is the operator for function composition because in definition x |> f
, the right side of the operator must be a function.
(add2 |> times3)
throws "NaN
Sure, in (add2 |> times3)
, add2
does not fit the type for the function times3
, so, what's wrong with this NaN
?
On the other hand, F# |> is not associative:
I'm sorry you are wrong.
As I already illustrated in my previous post, F# |>
is simply a function application operator that has a monad structure, which satisfies Associativity.
https://wiki.haskell.org/Monad_laws
In JavaScript,
Array.map() is known as an endo-functor and this one is not associative.
In ES2019, Array.flat() and Array.flatMap() has been newly introduced.
It is identical to a
map()
followed by aflat()
of depth 1, but slightly more efficient than calling those two methods separately.
Why do we need this?
tc39/proposal-flatMap#10 @dtipson
This proposal essentially gives native Arrays the methods they need to work as Monads (without having to extend the prototype).
With the robust aspect, flatMap
gains more ability that can't be done with map
const array1 = [1, 2, 3, 4, 5];
const array2 = array1
.flatMap(a => [a, a * 2]);
console.log(array2);
[ 1, 2, 2, 4, 3, 6, 4, 8, 5, 10 ]
const oddFilter = a =>
a % 2 === 1
? unit(a)
: [];
const array1 = [1, 2, 3, 4, 5];
const array2 = array1
.flatMap(oddFilter);
console.log(array2);
[ 1, 3, 5 ]
Array.flatMap() is a Monad method that has associative property in definition.
In the same manner, F# |>
is a Monad operator that has associative property in definition.
Having said that,
value |> foo(1, ^) |> bar(2, ^) ==
(value |> foo(1, ^)) |> bar(2, ^) ==
value |> (foo(1, ^) |> bar(2, ^))
This is interesting and I have no idea what's going on.
What does (foo(1, ^) |> bar(2, ^))
mean??
Since a programmer recognize the pipeline operator as f(x) === x |> f
, simply as a function application, I understand
value |> (foo(1, ^) |> bar(2, ^))
means
value |> someFunction
(remember referential transparency argument in my first post)
Is (foo(1, ^) |> bar(2, ^))
a function?
If so, does this |>
works as function composition of n-ary functions??
(foo(1, ^) |> bar(2, ^)) ===
(foo(1, ^) . bar(2, ^))
??
In hack world, the operator |>
behaves as not only function application but also function composition?? |>
== .
??
function application is not function composition, and the confusion should be strictly prohibited.
function composition of n-ary functions does not make sense.
Everything of hack-pipe "operator" seems very wrong in mathematical sense..
Could you explain please? @nicolo-ribaudo
Thanks.
As I already illustrated in my previous post, F#
|>
is simply a function application operator that has a monad structure, which satisfies Associativity.
Several people already showed you that F# pipe transplanted into JS is not associative. If you want to prove associativity (i.e. that rearranging parentheses doesn't change the outcome), you cannot replace operators to fit your claim. Just like I cannot prove that division is associative with this:
(a / b) / c === a / (b * c)
Is
(foo(1, ^) |> bar(2, ^))
a function?
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. If we denote the RHS expression as a function of the topic, the Hack pipe operator is associative:
( x |> (f(^) |> g(^)) ) == ( x |> g(f(^)) ) == g(f(x))
( (x |> f(^)) |> g(^) ) == ( f(x) |> g(^) ) == g(f(x))
The proposed Hack pipe in JS operates on parse nodes, not run-time values. Perhaps that's what confuses you.
Several people already showed you that F# pipe transplanted into JS is not associative.
Please note: I'm not talking about F# implementation, but talking about Algebra. That is the on-topic here.
If you want to prove associativity (i.e. that rearranging parentheses doesn't change the outcome), you cannot replace operators to fit your claim. Just like I cannot prove that division is associative with this:
You don't understand.
Function composition .
is associative, and this is Monoid and Monad
(a . b) . c ===
a . b . c ===
a . (b . c)
at the same time,
Function application |>
is not associative as Monoid but associative in Monad.
(a |>f) |> g ===
a |> f |> g ===
a |> (x => x |> f |> g) ===
a |> (f . g)
I'm sorry it seems you have not studied the Haskell page I linked:
https://wiki.haskell.org/Monad_laws
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)
you cannot replace operators to fit your claim
No, I just did the math, Ok?
It's not a function at run-time, it's not even legal syntax on its own.
So a member here claimed the hack |>
is associative and monoid : #223 (comment) @nicolo-ribaudo
a * b * c
==
(a * b ) * c
==
a * (b * c)
value |> foo(1, ^) |> bar(2, ^) ==
(value |> foo(1, ^)) |> bar(2, ^) ==
value |> (foo(1, ^) |> bar(2, ^))
Now, you (@lightmare) claim
(foo(1, ^) |> bar(2, ^))
is not even leagal synatax on its own.
In mathematics, there is no such situation (b * c)
is not even leagal syntax on its own.
So here we have confirmed the fact hack |>
is no longer a math operator and breaks the law of algebra.
Any objections?
I'm sorry it seems you have not studied the Haskell page I linked:
https://wiki.haskell.org/Monad_laws
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 ofg
andh
Associativity:(m |> g ) |> h === m |> (g . h)
Thanks for the explanation. I find it super confusing that you're using two different meanings of associativity in your posts, and sometimes couldn't tell which one you're talking about. You were not proving that |>
is associative (in algebraic sense), but the monad law of associativity over |>
.
Now, you claim
(foo(1, ^) |> bar(2, ^))
is not even leagal synatax on its own.In mathematics, there is no such situation
b * c
is not even leagal syntax on its own.
The part of the expression you used is only valid on the right-hand side of the pipe operator. If you want some comparison to math, it's as if you wrote a lone superscript Β²
without the argument you're squaring. A superscript without anything below it is not valid syntax.
you're using two different meanings of associativity in your posts,
It's the identical meaning in algebra and just layer is in Monoid (S = S * S) or Monad (M = M * F) .
I don't know what you mean by super script, here I only describes the algebra laws, and you admit (b * c)
is not a valid syntax in Hack-pipe during our conversation of the associativity of a * b * c
. You have corresponded right? Now I feel you try to get out from our context.
Any objections against hack |> is no longer a math operator and breaks the law of algebra?
Why does it matter if it's a math operator or not, exactly? The goal of the proposal isn't "provide a math operator that follows some set of laws", it's "to help linearize nested function calls". If that's achieved with something that follows X set of laws, great! If not, that's fine!
The goal of the proposal isn't "provide a math operator that follows some set of laws", it's "to help linearize nested function calls".
Is there such a consensus? Do we share the fact hack pipe is no longer satisfies a very basic law of algebra that is Associativity?
Do they know the "operator" of function composition f(x)
breaks the math?
Have you compared PRO and CON of F# doesn't violate the law of math, and on the hand Hack violates?
Not fine at all.
@stken2050 the term "consensus" with tc39 proposals typically only refers to consensus among TC39 delegates; the goals of a proposal are usually dictated by its champions.
I think if you want to be persuasive, you'll have to demonstrate concrete impacts - in the same way as "Promises aren't monadic!" didn't end up persuading the committee, "hack pipes aren't algebraic" likely won't either.
@ljharb
Great point. "Promises aren't monadic!" true, but it's endurable for FP community because
- Promises does not break the law of algebra, sure it's not Monad but at least an endo-functor.
- They basically can ignore it, even FP community feel it's a false product in JS, they use their own libraries such as Fluture.
And yes, I would not consume my time if it's for Promises, but this is about a very basic binary-operator that breaks the very basic law of Algebra.
it's "to help linearize nested function calls". If that's achieved with something that follows X set of laws, great! If not, that's fine!
@ljharb
Perhaps, I think my initial reaction to your comments is not appropriate in terms of my own topic, and did not help for inquiry much, so I would like provide more answer here.
In fact, I thought I mentioned in this aspect in my initial post here:
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
Replacements an expression of OOP style to a binary operator in an algebraic sense make the code concise and readable, easier to grasp the math structure which leads us to avoid mistakes then the code becomes robust. This is a very powerful approach, and perhaps some people observe this manner as FP code. Haskell codes are full of binary operators, and Haskellers basically do just Math/algebra in their code.
A replacement of a syntax
f(x)
== x |> f
g(f(x))
== x |> f |> g
is the same league of **
binary operator.
"to help linearize nested function calls" is fundamentally achieved by the implementation of the binary operator that is essentially a algebraic structure, and that is called expressions.
https://en.wikipedia.org/wiki/Expression_(computer_science)
In computer science, an expression is a syntactic entity in a programming language that may be evaluated to determine its value.[1] It is a combination of one or more constants, variables, functions, and operators that the programming language interprets (according to its particular rules of precedence and of association) and computes to produce ("to return", in a stateful environment) another value. This process, for mathematical expressions, is called evaluation.
Binary operator is in math realm in definition, so this should strictly follow the math rules.
In contract, there are statements
https://en.wikipedia.org/wiki/Statement_(computer_science)
In computer programming, a statement is a syntactic unit of an imperative programming language that expresses some action to be carried out.[1] A program written in such a language is formed by a sequence of one or more statements. A statement may have internal components (e.g., expressions).
Why expressions are safer and make better building blocks
Expressions vs. statements
C# and most imperative languages make a distinction between expressions and statements and have rules about where each kind can be used. But as should be apparent, a truly pure functional language cannot support statements at all, because in a truly pure language, there would be no side-effects.
Even though F# is not pure, it does follow the same principle. In F# everything is an expression. Not just values and functions, but also control flows (such as if-then-else and loops), pattern matching, and so on.
Basically, there is not much space to have fun for us to tweak specifications for expressions-binary operator because essentially the specifications are fixed in mathematics.
On the other hand, statement is for specifications that probably you are more interested in.
"to help linearize nested function calls" is not achieved by statements of human designed.
The problem of hack-style is this is a work of crossing the border between statement and expression, which we should avoid.
@js-choi and other often mentions "interoperability" and the concept is used to justify the crossing the border.
In Haskell, expressions or operator is not allowed to violate the math rules for interoperability.
Do notation is a statement of Haskell which is simply a syntax sugar on the monad operator |>=
, and the operator itself is strictly defined.
For the interoperability of |>
, we should have a minimal/F# pipe and PFA is the one to help.
According to Brief history of the JavaScript pipe operator
, I see to fit |>
to async
await
(statements) has been hazard to TC39.
Actually, it's not an adequate manner to fit binary-operator that is defined in math to a statement by human design.
In fact, I have never seen the math operator in JavaScript is spoiled in math sense in order to fit to some statement in JavaScript.
For how much impact will be brought due to the math inconsistency.
Honestly, I don't know.
What I know is we are not as smart as we can predict. The failure of OOP is a major evidence to me, for that any human invention over the math leads us a disaster of software development.
Object-Oriented Programming β The Trillion Dollar Disaster
Designers of OOP language have misunderstood they were smarter than math structure and invented various "best" mechanism for them.
C++, or Java and Ruby some other OOP languages got very unpopular and Rust that has a functional feature becomes very popular.
At the risk of inciting debate, programming isn't mathematics.
Programming become much more mathematics in this decade.
https://doc.rust-lang.org/reference/statements-and-expressions.html
Statements and expressions of Rust
Rust is primarily an expression language. This means that most forms of value-producing or effect-causing evaluation are directed by the uniform syntax category of expressions. Each kind of expression can typically nest within each other kind of expression, and rules for evaluation of expressions involve specifying both the value produced by the expression and the order in which its sub-expressions are themselves evaluated.
In contrast, statements in Rust serve mostly to contain and explicitly sequence expression evaluation.
Why expressions over statement?
Because expressions are math.
Statements, in contract, are not math but mechanisms designed by human ideas without any evidence but with their personal experience, such as
public class ForExample {
public static void main(String[] args) {
//Code of Java for loop
for(int i=1;i<=10;i++){
System.out.println(i);
}
}
}
this is the form of failed software development.
Now Rust is a language of expressions, functional (math) language.
Popularity of TypeScript
The popularity of TypeScipt is also an evidence of JS community become to respect Math more than ever. Why?
Because fundamentally type is sets of Map (mathematics) == function
https://ncatlab.org/nlab/show/type+theory#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.
In JavaScript we prefer much more strictness of the definition of functions.
Our way to treat Function in JS
https://en.wikipedia.org/wiki/Function_(mathematics)
In mathematics, a function is a binary relation between two sets that associates each element of the first set to exactly one element of the second set.
binary relation between two sets
This is the brief definition of function, and since we need to define Sets property we have "type" in programming.
Unfortunately, there is no native type system in JS, many uses TypeScript.
One more factor is binary relation, therefore we have binary operator |>
.
The |>
has been existed even before human exists, so the operation of binary relation is not a a thing like
Why does it matter if it's a math operator or not, exactly? The goal of the proposal isn't "provide a math operator that follows some set of laws", it's "to help linearize nested function calls".
Since this is also the very basic factor as Sets==Type, we need to implement this binary operator to JS with respectfully, carefully without messing up the Math structure.
remotely any difference whatsoever if a binary operator is associative; that algebraic laws matter even a little.
What do you mean by the word of "remotely"?
There is a difference if it does not corresponds to the math definition.
To me taking a risk to disrespect math itself is a huge risk. I feel very insecure.
As long as we stick to math as best as possible, we become more secured to write our safer code.
What i mean is, it does not make a difference. I still fail to understand why - beyond philosophical/ideological concerns - it would have any concrete negative impact if things don't "follow math".
What do you mean by the word of "remotely"?
What i mean is, it does not make a difference.
This is not established as an answer for my question at all.
May I ask how are you so sure it does not make a difference on the difference of the math? I think you have failed to express your reasoning here.
I did explain my reasoning for the concrete negative impact if things don't "follow math".
"philosophical/ideological concerns" does not reflect our reality as long as the thing have not happened yet.
My "philosophical/ideological concerns" corresponds to the reality that actually have happened in the history of our software development, which I should call "evidence".
it would have any concrete negative impact if things don't "follow math".
Programming is a math. Please refer the word of "Computation".
Haskellers just do algebra in their code but they also take advantage of a syntax of statement Imperative Programming for human being, and the point is they have never broken the the mathematical structure of the binary operators.
Hack pipe is the first binary operator that breaks algebraic structure (function application) in JavaScript.
I'm closing this issue. While F#-style pipes can be understood in terms of functional algebra, JS does not in general require that of any of its features. And fwiw, expression algebras also exist, and Hack-style pipes should be expressible in terms of them.
Regardless, tho, the issue raised by this thread isn't something we worry about in JS.