Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

[CS2] Output ES2015 arrow functions, default parameters, rest parameters#4311

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
GeoffreyBooth merged 48 commits intojashkenas:2fromGeoffreyBooth:arrow-functions
Oct 26, 2016

Conversation

GeoffreyBooth
Copy link
Collaborator

WIP. Just thought I’d get the ball rolling and try to establish a precedent for merging into the2 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:

test"`new` works against bare function",->eqDate,new->eqthis,new=>thisDatetest"#2009: don't touch `` `this` ``",->nonceA= {}nonceB= {}fn=null  (->fn==>thisis nonceAand`this`is nonceB  ).call nonceAokfn.call nonceB

@lydell actuallycommented 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, @'
varfoo;foo=(bar)=>{returnconsole.log(bar,this);};

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?

jakesjews reacted with thumbs up emoji
@GeoffreyBoothGeoffreyBooth added this to the2.0.0 milestoneSep 20, 2016
@lydell
Copy link
Collaborator

new(function(){})// Valid JavaScriptnew(()=>{})// 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 witharguments, which is not available in fat arrows.

a= (args...)=> args

@connec
Copy link
Collaborator

Are there no tests that include both fat arrows and parameter splats? I'd expect that to fail, since splats are implemented with arguments, which is not available in fat arrows.

There's probably a whole debate to be had about whether we reallywant to output ES2015 arrow functions. I guess we do, but perhaps we should add special handling forarguments? Perhaps not since it's very 'magical'. But then users need to remember that there's a concrete difference between-> and=> beyond the value ofthis.

@connec
Copy link
Collaborator

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')# }

@lydell
Copy link
Collaborator

@connecarguments is not a problem. Here’s a lazy solution:

a=(..._arguments)=>{  ...}

@connec
Copy link
Collaborator

OK, I guess I was just wondering aloud whether or not we should continue to deal witharguments in fat arrow functions or whether we should adopt ES6 behaviour (arguments is undefined).

E.g. what should this CS produce:

()=>arguments

Currently it compiles to:

function(){returnarguments;}

With this change:

()=>{returnarguments;}

Ultimately, would we want to catch calls toarguments in bound functions and set them up to instead output:

(..._arguments)=>{return_arguments;}

@lydell
Copy link
Collaborator

I think it makes sense to simply disallowarguments in fat arrow functions.

connec reacted with thumbs up emoji

@bjmiller
Copy link

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?

@lydell
Copy link
Collaborator

@bjmiller I would be very interesting to hear what makes CS fat arrows better! :)

@GeoffreyBooth
Copy link
CollaboratorAuthor

Re the one-line syntax for arrow functions, I think we shouldn't bother. We don't outputif foo then bar() ora = { b: 1 } as one-liners.

Rearguments, one of the goals of the 2.x effort is to align more closely with ECMAScript. Part of that involves no more sugar to “correct” ECMAScript’s oversights as we may perceive them, primarily because ES is evolving so fast and they might fix said oversight themselves soon enough and we don't want our polyfill to behave differently than their new syntax. In the case ofarguments specifically, we should just follow the spec: if they left it out, presumably they have their reasons, and it's no more difficult in CoffeeScript to need to know that arrow functions lackarguments than it is to know the same in JavaScript.

@atg
Copy link

atg commentedSep 20, 2016

My intuition of CoffeeScript functions is that-> has a freethis but=> has a boundthis. That's it. It's kind of weird forarguments to depend on the whether this was bound or not. If you are going to banarguments then ban it from both or neither.

kirly-af reacted with thumbs up emoji

@lydell
Copy link
Collaborator

I wouldn't mind "banning"arguments altogether – in fact, I don't think I'veever used in in CoffeeScript, since we have splat parameters.

@atg
Copy link

atg commentedSep 20, 2016
edited
Loading

Ipersonally would not mourn the loss ofarguments, however removing it has a high potential to break existing code. ¯_(ツ)_/¯

My view is that it's desirable for-> and=> in CoffeeScript to behave as identically as possible, and not to introduce too many surprises or gotchas. The current (ES3) implementation performs perfectly in this regard. It bindsthis and otherwise works exactly the same as->.

In some ways, ES6 arrows are worse. You can'tyield inside an ES6 arrow, so CoffeeScript would have to fall back to the current implementation to continue to support that combination of features.

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.

@GeoffreyBooth
Copy link
CollaboratorAuthor

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 declareself = this in the enclosing function, like the old days. This doesn’t strike me as terrible. Likewise, having to write(args...) => instead of referencing the magicarguments object also doesn’t strike me as terrible.

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.

@lydell
Copy link
Collaborator

I was confused in my previous comments.arguments is notdisallowed in ES arrow functions. They refer to the outsidearguments (just likethis).

$ ./bin/coffee -e 'console.log(((a...) -> a) 1, 2, 3)'[ 1, 2, 3 ]$ ./bin/coffee -e 'console.log(((a...) => a) 1, 2, 3)'[]

The second example returns an empty array, because of thearguments from CoffeeScript's IIFE wrapper.

I'm surprised that there were no tests for this! (Both examples above should of course log the same thing:[1, 2, 3]).

@GeoffreyBooth
Copy link
CollaboratorAuthor

GeoffreyBooth commentedSep 21, 2016
edited
Loading

To complete@lydell’s example, here’s the ES2015 equivalent:

console.log(((...a)=>a)(1,2,3))// [1, 2, 3]

So thereis a way to access a limitless number of arguments in an arrow function, so long as we make sure to compile... into ES2015... in any parameters (e.g.args... to...args).See also.

That just leaves the generator issue. Is it really so common to make a bound function generator? Isself = this in the outer scope not good enough for what I presume to be an uncommon use case?

@GeoffreyBoothGeoffreyBooth changed the titleOutput ES2015 arrow functions[wip] Output ES2015 arrow functionsSep 25, 2016
@GeoffreyBooth
Copy link
CollaboratorAuthor

GeoffreyBooth commentedSep 25, 2016
edited
Loading

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

vara;a=(...args)=>{returnargs;};

Since we’re not convertingargs... into ES5 code involvingarguments anymore, we needn’t worry aboutarguments within arrow functions. This also fixes your other example:

./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 thelast parameter. So now we have two failing tests:

  test/function_invocation.coffee:173    mult = function(x, ...mids, y) {                              ^SyntaxError: Rest parameter must be last formal parameter  test/functions.coffee:124    arrayEq([0, 1], (function(...splat, _, _1) {                                      ^SyntaxError: Rest parameter must be last formal parameter

ThatSyntaxError is thrown by Node. I guess this should just become part of our syntax as well that you can’t use... except in the last parameter?

This also raises the question of what to do about expansions in parameter lists:

./bin/coffee -bpe '(a, ..., b) => splat'(() => {  var a, b;  a = arguments[0], b = arguments[arguments.length - 1];  return splat;});

@lydell
Copy link
Collaborator

Yes, splats have to be thelast 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 mergemaster into2 and then rebase this branch on2? It’s impossible to review this PR now with so much crap in the diffs.

@GeoffreyBooth
Copy link
CollaboratorAuthor

GeoffreyBooth commentedSep 25, 2016
edited
Loading

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

args, at the start of the function, is just[5]. Soargs.slice(-2) returns[5], thereby settingd to undefined. We can’t useargs as a substitute forarguments.

We can, however, recreatearguments (this works for arrow functions too):

f=function(a,b,c,d, ...args){letarguments=[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 5f(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: theslice lines require knowing how many arguments precede and follow the splat, so that you know the indices to slice by. The[c, d] part requires knowing which arguments follow the splat. It’s just a lot of processing, prone to edge cases I can’t imagine, for a feature that I can’t imagine people use all that often.

@GeoffreyBooth
Copy link
CollaboratorAuthor

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);}

Andf(1, 2, 3, 4, 5, 6, 7) still prints1 2 3 4 5 6 7.

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?

@lydell
Copy link
Collaborator

lydell commentedSep 26, 2016
edited
Loading

Sorry about that broken compilation example. It sure was a "something like." Here's a version that I've actually tested:

varf;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, Ihave used it in[first, middle..., last] = someArray.

Regardinga, ..., b: Yes, it is missing from the documentation. That's a shame. The feature is not very old (it was added in 1.7.0), and is useful for getting the last item of an array:[..., last] = someArray. That's used quite a bit in CoffeeScript's source code. When it comes to compiling, it's no different thana, args..., b, except thatargs won't be available for the user to use.

Regarding your suggested compilation:(...args) => means that we loose.length of the function. That's true for todays' compiled output too, which is a shame. Redux preserves.length. I think we should, too, when we have the chance.

Summary:

  • I've never missed non-last-splat-parameter when writing ES2015, but Ihave missed it in array destructuring.
  • I believe that dropping support for splats-anywhere-in-the-list will be a major backwards incompatibility hurdle for people.
  • We already have lots of code for dealing with it.
  • My suggested output is just one extra line of code.

@lydell
Copy link
Collaborator

Here's an example with parameter defaults:

// f = (a = 1, b = 2, args..., c = 3, d = 4) ->varf;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) ->varf;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)}

@GeoffreyBooth
Copy link
CollaboratorAuthor

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?

@lydell
Copy link
Collaborator

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:

  • Remove expansion.
  • Make splats work like in ES everywhere.
  • Flip the... to the ES side (...args instead ofargs... – this regularly trips me up when switching between the languages ;) )

@GeoffreyBooth
Copy link
CollaboratorAuthor

@connec@jashkenas@lydell Not to short-circuit the discussion incoffeescript6/discuss#51, but can we merge in this PR as is? And if we ultimately decide to go the other way regardingnull, I can submit another PR. I don’t feel strongly either way on that question, but I want to get this change merged in so that the classes PR can start using it.

@GeoffreyBoothGeoffreyBooth changed the titleOutput ES2015 arrow functions, default parameters, rest parameters[CS2] Output ES2015 arrow functions, default parameters, rest parametersOct 23, 2016
@lydell
Copy link
Collaborator

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.

@GeoffreyBooth
Copy link
CollaboratorAuthor

So I just tried running the tests in this branch under--harmony mode, and I got a surprise. This test fails only in harmony (generators.coffee:55):

test"bound generator",->obj=bound:->do=>yieldthisunbound:->do->yieldthisnested:->do=>yielddo=>yielddo=>yieldthis

It only fails in harmony mode because we’re not runninggenerators.coffee except in harmony mode, because back when generators were added to CoffeeScript that’s the only way they could be run. Now with Node 6 the--harmony is unnecessary, and will be removed as soon as#3757 lands.

Anyway the reason this test fails is because of theyield this inside a bound function. For a simpler example, this:

f==>yieldthis

compiles (via this PR) to this:

f=()=>{return(yieldthis);};

which Node doesn’t like:

return(yieldthis);^^^^SyntaxError:Unexpectedtokenthis

becausearrow 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 thatyield is inside a bound function as opposed to a regular function, and throw an error in the former case.

The workaround, ironically enough, is to just use a regular function and the oldself = this hack:

self=thisf=->yield self

… add check to throw error if `yield` appears inside a bound (arrow) function
@GeoffreyBooth
Copy link
CollaboratorAuthor

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 withoutyield. Hopefully bound generator functions aren’t so common, and we’re only aligning ourselves with ES here by forcing people to do theself = this rigamarole for bound generator functions. I’ve added this to thebreaking changes wiki document.

@lydell, others, is there anything I’m missing here? If not, please let me know and I’ll merge this PR into2.

assertErrorFormat 'f = => yield this', '''
[stdin]:1:5: error: bound (fat arrow) functions cannot contain 'yield'
f = => yield this
^^^^^^^^^^^^^
Copy link
Collaborator

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 underlineyield only?

Copy link
CollaboratorAuthor

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>   1quote>   2quote>   yield this'[stdin]:1:5: error: bound (fat arrow) functions cannot contain 'yield'f = =>    ^^

@GeoffreyBooth
Copy link
CollaboratorAuthor

Updated. Now it only underlines theyield line (assuming I understand whato.scope.method refers to):

./bin/coffee -bpe 'f = =>  1  2  yield this'[stdin]:4:3: error: yield cannot occur inside bound (fat arrow) functions  yield this  ^^^^^^^^^^

@GeoffreyBooth
Copy link
CollaboratorAuthor

@lydell any other notes?

@lydell
Copy link
Collaborator

lydell commentedOct 26, 2016
edited
Loading

No :)

@GeoffreyBoothGeoffreyBooth merged commitfb2be8e intojashkenas:2Oct 26, 2016
@GeoffreyBoothGeoffreyBooth deleted the arrow-functions branchOctober 26, 2016 05:26
@GeoffreyBoothGeoffreyBooth mentioned this pull requestMar 24, 2017
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@connecconnecconnec left review comments

@lydelllydelllydell left review comments

Assignees
No one assigned
Labels
Projects
None yet
Milestone
2.0.0
Development

Successfully merging this pull request may close these issues.

10 participants
@GeoffreyBooth@lydell@connec@bjmiller@atg@vendethiel@jashkenas@Jessidhia@nickdima@carlmathisen

[8]ページ先頭

©2009-2025 Movatter.jp