-
Notifications
You must be signed in to change notification settings - Fork 2k
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
[CS2] Output ES2015 arrow functions, default parameters, rest parameters #4311
Conversation
new (function () {}) // Valid JavaScript
new (() => {}) // Runtime error. Both tests should be removed, in my opinion. Are there no tests that include both fat arrows and parameter splats? I'd expect that to fail, since splats are implemented with a = (args...) => args |
There's probably a whole debate to be had about whether we really want to output ES2015 arrow functions. I guess we do, but perhaps we should add special handling for |
Just another thought (forgive me if this is already handled in the PR): would we want to special-case single-JS-statement fat arrow functions and generate shorthand methods in that case? a = => console.log 'hello'
# var a = () => console.log('hello')
a = => console.log 'hello' ; console.log 'world'
# var a = () => {
# console.log('hello')
# console.log('world')
# } |
@connec a = (..._arguments) => {
...
} |
OK, I guess I was just wondering aloud whether or not we should continue to deal with E.g. what should this CS produce: () => arguments Currently it compiles to: function() {
return arguments;
} With this change: () => {
return arguments;
} Ultimately, would we want to catch calls to (..._arguments) => {
return _arguments;
} |
I think it makes sense to simply disallow |
I vote no for outputting es6 fat arrows. Original CS fat arrows are objectively better, and there is no advantage to having es6 arrows in the compiled code. Can everyone please stop trying to add es6 things to the compiled output just because they're new? |
@bjmiller I would be very interesting to hear what makes CS fat arrows better! :) |
Re the one-line syntax for arrow functions, I think we shouldn't bother. We don't output Re |
My intuition of CoffeeScript functions is that |
I wouldn't mind "banning" |
I personally would not mourn the loss of My view is that it's desirable for In some ways, ES6 arrows are worse. You can't So I agree with @bjmiller there's really nothing wrong with the way things currently work, and it has the added benefit of being compatible with ES3 and ES5 as well. |
If you want to create a bound generator function after this change, you would do so the same way in CoffeeScript as you would in ECMAScript: just declare What’s “wrong” with current arrow functions is that they unnecessarily deviate from the ES2015 spec. Now that JavaScript has arrow functions, we should output arrow functions. Anything else is counterintuitive. |
I was confused in my previous comments.
The second example returns an empty array, because of the I'm surprised that there were no tests for this! (Both examples above should of course log the same thing: |
To complete @lydell’s example, here’s the ES2015 equivalent: console.log(((...a) => a)(1, 2, 3))
// [1, 2, 3] So there is a way to access a limitless number of arguments in an arrow function, so long as we make sure to compile That just leaves the generator issue. Is it really so common to make a bound function generator? Is |
…arameter syntax) output that parameter as ES2015
Okay @lydell, I did some work on splats/rest parameters. I could do a lot more to eliminate now-unnecessary code, but I just want to get this working first. Anyway, as of my latest commit your example: a = (args...) => args becomes var a;
a = (...args) => {
return args;
}; Since we’re not converting ./bin/coffee -e 'console.log(((a...) => a) 1, 2, 3)'
[ 1, 2, 3 ] An interesting disadvantage of the ES2015 rest parameter syntax is that the rest parameter must be the last parameter. So now we have two failing tests:
That This also raises the question of what to do about expansions in parameter lists:
|
Yes, splats have to be the last thing in ES. My first idea for handling that (rather than dropping support) was to do something like: f = (a, b, args..., c, d) -> f = function (a, b, c, d, ...args) {
[c, d] = args.slice(-2);
args = args.slice(0, -2);
}; Also, could you merge |
The diff should be sensible now. Your example doesn’t work for me: f = function (a, b, c, d, ...args) {
[c, d] = args.slice(-2);
args = args.slice(0, -2);
console.log(a, b, args[0], c, d);
};
f(1, 2, 3, 4, 5)
// 1 2 undefined 5 undefined
We can, however, recreate f = function (a, b, c, d, ...args) {
let arguments = [a, b, c, d].concat(args);
[c, d] = arguments.slice(-2);
args = arguments.slice(2, -2);
console.log(a, b, ...args, c, d);
};
f(1, 2, 3, 4, 5)
// 1 2 3 4 5
f(1, 2, 3, 4, 5, 6, 7)
// 1 2 3 4 5 6 7 I guess the question is whether we should. This strikes me as a lot of extra JavaScript to be outputting. I suppose we should do it to prevent breaking backward compatibility, but it feels messy. It’s also so much more difficult to figure out: the |
Better version without intermediate variable: f = (a, b, args..., c, d) =>
console.log a, b, args..., c, d becomes: f = (...args) => {
[a, b] = args.slice(0, 2);
[c, d] = args.slice(-2);
args = args.slice(2, -2);
console.log(a, b, ...args, c, d);
} And This is quite complicated to produce, so the question still stands: is this a feature we want to support? Likewise with expanding parameters: f = (a, ..., b) => This isn’t even mentioned in the docs, though it’s in the grammar. Are we keeping this too? |
Sorry about that broken compilation example. It sure was a "something like." Here's a version that I've actually tested: var f;
f = function (a, b, c, d, ...args) {
[c, d, ...args] = args.slice(-2).concat([c, d], args.slice(0, -2))
} Regarding that it is more complicated to compile: Yes, it is. But at the same time, that's what we've been doing here for like half a decade now. Regarding whether the feature is useful or not: I can't really think of a case (from the top of my mind) when I've used a splat parameter somewhere else than at the end. However, I have used it in Regarding Regarding your suggested compilation: Summary:
|
Here's an example with parameter defaults: // f = (a = 1, b = 2, args..., c = 3, d = 4) ->
var f;
f = function (a = 1, b = 2, c, d, ...args) {
[c = 3, d = 4, ...args] = args.slice(-2).concat([c, d], args.slice(0, -2))
console.log(a, b, args, c, d)
} And here's an example with destructuring: // f = ({a = 1} = {}, b, args..., c, d) ->
var f;
f = function ({a = 1} = {}, b, c, d, ...args) {
[c, {d = 4} = {}, ...args] = args.slice(-2).concat([c, d], args.slice(0, -2))
console.log(a, b, args, c, d)
} |
I was only referring to function parameters. Limiting splats to the last argument in a function parameter list, the way ES does, doesn’t mean we would need to stop using splats in any position in destructuring. Assuming splats elsewhere are untouched, are you still sure we want to support splats at any function parameter position? And expansions at any function parameter position? |
I've never been sure :) Personally, I wouldn't mind dropping splats at any position in parameter lists. And dropping expansion in parameter lists. The only "bad" thing is that array destructuring and "parameter list destructuring" would no longer be equivalent. The following would no longer be true: f = (<params>) ->
# equivalent to:
f = (args...) ->
[<params>] = args Couldn't we use the same code in both cases? Another route we could take, is to:
|
@connec @jashkenas @lydell Not to short-circuit the discussion in coffeescript6/discuss#51, but can we merge in this PR as is? And if we ultimately decide to go the other way regarding |
If merging this helps you who actually work on the 2 branch, then go for it, I'd say. The 2 branch doesn't need be be as stable as master. |
So I just tried running the tests in this branch under test "bound generator", ->
obj =
bound: ->
do =>
yield this
unbound: ->
do ->
yield this
nested: ->
do =>
yield do =>
yield do =>
yield this It only fails in harmony mode because we’re not running Anyway the reason this test fails is because of the f = => yield this compiles (via this PR) to this: f = () => {
return (yield this);
}; which Node doesn’t like: return (yield this);
^^^^
SyntaxError: Unexpected token this because arrow functions cannot be generators. So this is another breaking change as a result of this PR. And I need to find a way to check that The workaround, ironically enough, is to just use a regular function and the old self = this
f = -> yield self |
… add check to throw error if `yield` appears inside a bound (arrow) function
Fixed. Everyone okay with this change? I suppose the alternative is to fall back to the CS 1.x output for bound functions in the event of a bound generator function, but I feel like that will cause more debugging trouble than it’s worth, by potentially introducing inconsistencies between bound functions with and without @lydell, others, is there anything I’m missing here? If not, please let me know and I’ll merge this PR into |
assertErrorFormat 'f = => yield this', ''' | ||
[stdin]:1:5: error: bound (fat arrow) functions cannot contain 'yield' | ||
f = => yield this | ||
^^^^^^^^^^^^^ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What will this look like if the function is multi-line? Is it better to underline yield
only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It just underlines whatever the first line of the function is, starting at the =>
:
./bin/coffee -bpe 'f = =>
quote> 1
quote> 2
quote> yield this'
[stdin]:1:5: error: bound (fat arrow) functions cannot contain 'yield'
f = =>
^^
Updated. Now it only underlines the
|
@lydell any other notes? |
No :) |
WIP. Just thought I’d get the ball rolling and try to establish a precedent for merging into the
2
branch. I’ve only spent a few minutes on this, and I was surprised how easy it was to get=>
to output as=>
; although I get the sense that there are a lot of considerations to=>
that I’m not considering. Please enlighten me.Only two tests fail with this patch, but I’m not sure that they should pass:
@lydell actually commented back when he wrote the second test that it would fail when we output
=>
. So maybe it’s time to remove that test?As for the first one, I get the sense that the test was written expecting the arrow function to be wrapped by an IIFE. I presume that the point of outputting
=>
for=>
is to get our output closer to its input, and therefore we shouldn’t be dropping unexpected wrapper functions into the output:./bin/coffee -bpe 'foo = (bar) => console.log bar, @'
Is this correct? Or should this test pass somehow?
There must be lots of additional code scattered across the repo that can be removed now that we needn’t do so much work to parse
=>
. What cleanup needs to be done?