-
Notifications
You must be signed in to change notification settings - Fork 108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How about overloading |
instead?
#190
Comments
It's not always const Nums = Object.fromEntries(
["zero", "one", "two", "three", "four", "five"].map((name, val) =>
[
name,
Object.assign(x => x * val, { valueOf: () => val })
]
)
);
console.log(typeof Nums.two); // "function"
console.log(Nums.one + Nums.two); // 3
console.log(Nums.two(Nums.three)); // "take two 3s", 6
console.log(Nums.five - Nums.three); // 2
console.log(Nums.five / 10); // 0.5
console.log(4 + Nums.three); // 7
// And this:
console.log(4 | Nums.two); // 6 Also, the |
Thanks for the input. I forgot to update the |
Going to express radical opinion.
Things like whitenoise() | lpf(440, 100) | compress() | speaker(.75)
<a>{ t`hello` | doubleSay | capitalize | exclaim }</a> are possible in JS[X] already. Looking more at jsPipe - less it seems a joke. I can imagine similar quality package done seriously, mb using BigInts. Somebody like RxJS: // current API
fromEvent(document, 'click')
.pipe(
throttleTime(1000),
map(event => event.clientX),
scan((count, clientX) => count + clientX, 0)
)
.subscribe(count => console.log(count));
// pipe API
let clicks = fromEvent(document, 'click') | throttleTime(1000) | map(event => event.clientX)
subscribe(clicks, count => console.log(count)) So pipe syntax can be done organically via |
Reusing
|
Practically much possible. That's even defined programming task.
Yes, as pipe operator as well. Every operator has a meaning and wide usage, nonetheless there's overloading proposal and
Direction is present in code itself, what's the point to underline it with |
you can't overload the bitwise OR const falsy = () => false
if (true | falsy) {
console.log("true")
} else {
console.log("false")
} using the bitwise operator this logs but if |
@jonatjano You're just rewording what has been acknowledged in the OP. Show me six real-world examples of a function on the right-hand side of |
most minifier I know tend to change to order of conditions to put variable values behind constant values : using const someFunction = () => Math.random() < 0.5 // create a boolean from other values
if (someFunction() | true) {
console.log("true")
} becomes const someFunction=()=>Math.random()<.5;!0|someFunction()&&console.log("true"); as you can clearly see, the function is now after the bitwise OR, meaning most code minified with UglifyJs 3 using at least one bitwise operator is potentially broken by your proposal, which is more than 6 I suppose EDIT : I just looked throught Google's result page scripts with the regex |
No, it's not. You have a function call on the right-hand side. The function gets called before the OR is evaluated. This would only break code where |
huh, well I did something dumb by adding these parenthesis, but if you remove the parenthesis the result stay the same, minifiers move the variable identifier to the right hand side of the operator, it doesn't treat functions differently than any other type of variable |
@jonatjano result of overloaded pipe/any operator now (via valueOf, like here) doesn't make much sense anyways and has to be unconverted. |
So what you're saying is that it would also break code where |
Minifiers assume safely assumption of input, preserving workable code (what's the point of minifier otherwise?). const v1 = {valueOf: () => 1}
const v2 = {valueOf: () => 2}
if (v2 | v1 | true) {
console.log("true")
} gives const v1={valueOf:()=>1},v2={valueOf:()=>2};v2|v1|!0&&console.log("true"); |
We still don't break existing code if there's no good reason to, and ambiguous parses are generally a strong negative. The (My coding font even has a ligature for it, turning |
Not sure what breaking code and parsing ambiguity you mean. From the precedence point (Go, Julia, Perl et al.), introducing Overloading proposal enables One problem of a | b + c | d
// is
a | (b + c) | d
// and not
(a | b) + (c | d)
// as one may think from chaining logic
a.pipe(b) + c.pipe(d)
// what's intuition for this?
a |> b + c |> d |
Another example of overloading pipe https://github.com/jussi-kalliokoski/babel-plugin-operator-overload |
There are literally trillions of HTML pages in the wild. It's generally reasonable to assume that any behavior that is legal has been used on at least a small % of those pages. If we change the behavior of Is it a large risk? Probably not. But taking such risks still needs to give a sufficiently worthwhile benefit, and "|> operator is ugly" isn't a very compelling one; the prevalence of the operator across multiple languages suggests you're in a minority with that opinion.
You end up citing the issue just below - the precedence of I think the problems with reusing |
Not sure where you got this, the proposition is not to change the That's unfortunate that you see I hoped language design is not opinion challenge. |
Operator overloading isn’t a thing yet. It’s not appropriate to block a proposal on another that hasn’t advanced past it. |
Isn't that how proposals get declined, left hanging, become obsolete or get revisioned, due to superseding by (indirect? more general?) competitor? I'm sorry, are pipes a thing already? Do you mean Stage 1 a couple of days ago? Also - is that appropriate to ignore alternatives based on opinion? The discussion doesn't seem fair - there's only criticism against |, but no comparison with |>. |
Pipes are stage 2 as of last week - but it’s not really appropriate for a stage 1 proposal to be blocked by another, in the general case. |
Yeah, layering proposals is sometimes appropriate - we want to make sure we have a coherent language if possible, and sometimes that means waiting for another proposal that we can build off of rather than creating a one-off solution - but not always. Especially in this case, operator overloading is still an immature proposal with little consensus (unfortunately), so it would be a significant delay for this proposal. As was said in earlier comments, tho, the precedence issues with Finally, using an overloaded |
a |> b(^) + c |> d(^)
// is
a |> (b(^) + c) |> d(^)
// and not
(a |> b(^)) + (c |> d(^))
// as one may think from chaining logic
a.pipe(b) + c.pipe(d) JS could be that: a|b + c|d Not that: (a|>b(^)) + (c|>d(^)) |
JS already is that first one; it has a meaning that can’t change. |
So far the only argument outlined against
Interesting to know what's implied by that, this? src | async src => await Promise.resolve(src)
Just want to clarify what tax are we talking about, this?
I only wonder if proper research of It is alarming that JS turns into Hack: // now:
b(a) + d(c)
// proposal:
(a |> b(^)) + (c |> d(^)) |
In src | async src => await Promise.resolve(src) The value of the pipe is now a promise. The next step of the pipeline can't get at the value inside the promise in a reasonable way: src | async src => await Promise.resolve(src) |> ??doSomethingWithSrcInPromise?? The only way around that is to abandon the pipe operator and switch into (src | async src => await Promise.resolve(src)).then(doSomethingWithSrcInPromise) Promise-returning functions are already common, and will only become more so. JS has tools to make dealing with promises easier - the
The README goes into detail about this.
I'm not sure why one would rewrite their expression to use pipes there - the function calls already seem perfectly reasonable. If they're not reasonable, because they're actually representing some heavily-nested stack of code, then the original expression isn't reasonable or readable either - you should be storing each expression in a variable and then adding those variables together. Pointing out that it's possible to write code badly is not, generally, an argument against a feature. You'd have to show that the new bad code can be expected to become common. |
Since some current libraries (RxJS, gulp, pull-stream, flyd, observable etc.) aren't going to win from Hack pipes even refactored (syntax tax is high, keyboard and visual noise), there's a room for an alternative. Also there's discontentment with Hack pipes anyways.
Cases of implicit/same-type argument from readme are common (current pipe / stream / chain libs ). RxJS pattern would be more widespread if there was pipe operator, now chaining is just simpler. // jQuery / dom, pipe
$('#el') |> fadeIn(^, 100) |> css(^, {border: '2px solid red'}) |> on(^, 'click', ()=>{...})
// vs |
$('#el') | fadeIn(100) | css({border: '2px solide red'}) | on('click', () => {...}) // RxJS (#167) pipe
someObservable
|> map(^, x => x + x)
|> filter(^, x => x % 2 === 0)
|> concatMap(^, n => interval(n)
|> take(^, 3))
|> subscribe(^, console.log)
// vs |
someObservable
| map(x => x + x)
| filter(x => x % 2 === 0)
| concatMap(n => interval(n) |> take(^, 3))
| subscribe(console.log) // gulp pipe
src('src/*.js') |> uglify(^) |> rename(^, { extname: '.min.js' }) |> dest(^, 'output/');
// vs |
src('src/*.js') | uglify() | rename({ extname: '.min.js' }) | dest('output/'); // web-audio / tuna pipe
input('./src.wav') |> gain(^, .6) |> chorus(^, { rate: 1.5, feedback: 0.2, delay: 0.0045, bypass: 0 }) | output(^)
// vs |
input('./src.wav') | gain(.6) | chorus({ rate: 1.5, feedback: 0.2, delay: 0.0045, bypass: 0 }) | output() Tax from Hack syntax has a bit different purpose than existing pipe solutions. It tries to glue code from different areas, as shown in readme (object → array → string): // js way
let keyVals = Object.keys(envars).map(envar => `${envar}=${envars[envar]}`);
console.log(chalk.dim(`$ ${keyVals.join(' ')}`, 'node', args.join(' ')));
// hack pipe
envars
|> Object.keys(^)
|> ^.map(envar => `${envar}=${envars[envar]}`) // why not just Object.keys().map()?
|> ^.join(' ')
|> `$ ${^}`
|> chalk.dim(^, 'node', args.join(' '))
|> console.log(^); Not clear why optimizing that, already well-solved in js way problem. Also anticipate breakage for static analyzers, bundlers and transforms (stuff like Many libraries are working on same-type objects, like stream, audio, array, file, animation, date, element etc., and misuse chaining, |
Also facebook/react#22295 😄 |
@dy It feels like you are mixing two unrelated discussions. One is F# vs Hack pipe operator, and the other one is which token to use.
|
@nicolo-ribaudo to illustrate tax for some common scenarios, these concerns come together. Also If there's a chance overriding |
I'm going to go ahead and lock this issue. The pipe operator is not going to switch to overloading |
Have you considered overloading an existing operator instead of introducing a new one? I looked around, and only found some mentions of possible future user-defined operator overloading, like C++ or Python have. That's not quite what I mean. I was thinking about overloading just
|
for functions, similar to how+
is overloaded for strings.Currently if
typeof Foo === 'function'
then(x | Foo) === (x | NaN) === (x | 0)
.Even though this operation is well-defined, I couldn't come up with a sensible use case where you'd have a calculation relying on the coercion
function ~> NaN
on the right-hand-side of|
.Pipe operator overload
I experimented a bit with AST explorer, rewriting
a | b
into:An upside of overloading an existing operator is that there'd be no changes to grammar. Everything would parse exactly as it parses now. Which could also be seen as a downside, in that it's essentially stripped-down minimal F# semantics without special rules for await/yield.
Another downside is not-immediately-obvious interaction with arrow function expressions:
It may be surprising to some, that the second arrow function is inside the first, meaning it has access to
x
. Others might consider that useful, I'm not sure. If such nesting is undesired, it can be prevented by parenthesizing each arrow function:This might not even be a big issue, because regardless of whether the functions are nested (as in the first example where the line breaks are deceptive), or individually parenthesized (as in the second example), the order of operations and the result will be the same (as long as the inner function doesn't reach for
x
, of course). Since the pipe operator is left-associative, arrow function gobbling up subsequent|
terms doesn't alter the sequence of calls:AST explorer snippet
Here's the experimental transform adding compile-time and run-time
|
overloads in AST explorer:https://astexplorer.net/#/gist/1d15ff515bc1dfab22e64820ea6b4a18/latest
It also overloads shift-right operators, which I'm not really suggesting. I was just trying different stuff, figured one could use a secondary operator to resolve promises before calling the right-hand-side, and
|
doesn't have a same-precedence "twin" for this purpose.The text was updated successfully, but these errors were encountered: