Skip to content

Named Closures #2205

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
ghost opened this issue Mar 20, 2012 · 33 comments
Closed

Named Closures #2205

ghost opened this issue Mar 20, 2012 · 33 comments
Labels

Comments

@ghost
Copy link

ghost commented Mar 20, 2012

Let's say we want to do this:

req.on('end', function onEnd() {
  console.log('winning');
});

Instead of this:

req.on('end', function() {
  console.log('losing');
});

How is that possible in CoffeeScript?

@paulmillr
Copy link

Not possible in pure coffescript. It was removed because of some leakage in ie or so. See #15.

@michaelficarra
Copy link
Collaborator

Yep, there's no need for NFEs, so we don't have them. Closing.

@rlidwka
Copy link

rlidwka commented Mar 20, 2012

They are useful for debugging purposes, so, there is need for them.

@samccone
Copy link

samccone commented Sep 4, 2012

+1

2 similar comments
@unity
Copy link

unity commented Oct 18, 2012

+1

@yuri-karadzhov
Copy link

+1

@epidemian
Copy link
Contributor

Hmmm.. this is quite interesting, if the function are attached directly to a variable (i.e. = ), then Chrome infers and shows nice names for the functions on the stack trace:

foo = ->
  bar()

bar = ->
  throw 'Yo, look at the stack trace!'

foo()​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​
# Uncaught Yo, look at the stack trace!
#   bar
#   foo
#   (anonymous function)
#   CoffeeScript.run
#   b
#   window.undefined.runScripts

But as soon as you change the assignment and let the function first go into another function, then the names are not inferred, which seems reasonable:

id = (x) -> x

foo = id ->
  bar()

bar = id ->
  throw 'Yo, look at the stack trace!'

foo()​​​
# Uncaught Yo, look at the stack trace!
#   (anonymous function)
#   (anonymous function)
#   (anonymous function)
#   CoffeeScript.run
#   b
#   window.undefined.runScripts

Now, as i understand from this article, it should be possible to name anonymous functions using the displayName property and then see those names on the debugger, but i couldn't make it work:

named = (name, fn) ->
  fn.displayName = name
  fn

foo = named 'myFoo', ->
  bar()

bar = named 'myBar', ->
  throw 'Yo, look at the stack trace!'

foo()​​​​​​​​​​​​​​​​
# Uncaught Yo, look at the stack trace!
#   (anonymous function)
#   (anonymous function)
#   (anonymous function)
#   CoffeeScript.run
#   b
#   window.undefined.runScripts

Maybe displayName works but i'm not using it correctly here. Does anyone have more experience with this?

Well... i was just going to propose using displayName in case one wanted more debugging information, but after failing at making it work, i now consider that named function expressions could be a better idea. Maybe a switch in the compiler?

@pke
Copy link

pke commented Dec 6, 2012

👍 needed that too for stack traces. Will patch it myself as @rlidwka suggested in #15

@mindplay-dk
Copy link

"there's no need for NFEs", yet, according to the documentation:

Constructor functions are named, to better support helpful stack traces

Because only constructor functions need helpful stack traces?

Probably one of the most asked-for features: #15 #2947 #3046 #1640 #2666 #2152

IMO named functions is an important JavaScript feature and CS is crippled without it...

@jashkenas
Copy link
Owner

Because only constructor functions need helpful stack traces?

That's not how it works -- try it. The last time I checked in Chrome, the debugger is able to infer the name of every function, but anonymous classes it was unable to. For that reason, CoffeeScript stack traces should appear complete, and completely named.

@pke
Copy link

pke commented Oct 10, 2013

How is Chrome able to get function names of unnamed functions?

someFunc () ->
  console.log("yeah");

should compile to

someFunc(function () {
  console.log("yeah");
});

How can Chrome give the anonymous function a name?!

@jashkenas
Copy link
Owner

Like I said -- try it. You'll be pleasantly surprised.

@pke
Copy link

pke commented Oct 10, 2013

Well, VisualStudio 2012 doesn't and its all I care about since I use CS to develop Windows Store apps. What is chrome spitting out as the function name?

Why is this feature debated so long?

@samccone
Copy link

@pke how else will we track the IP's in real time? http://www.youtube.com/watch?v=hkDD03yeLnU

@mindplay-dk
Copy link

I keep hearing "it's just JavaScript" from various peers, but apparently CS is more like a subset of JavaScript. (I know, "the good parts"...)

Putting the blame/responsibility on language vendors (Chrome) doesn't really address this missing feature.

I think part of the misunderstanding here, is that there are actually two varieties of function in JavaScript - technically both are expressions, but really one works more like a statement, and (due to bugs/limitations in older version of IE) you shouldn't mix the pure expression form (anonymous functions) and statement-like form.

In other words - expression form is good:

var foo = function () { ... };

And statement-like form is good:

function foo() {
}

Notice how you don't need the semi-colon at the end.

The following form is bad:

var x = function foo() {
};

Note the semi-colon at the end, which terminates the var x = statement, not the function-expression - which is now somewhat garbled and has some pretty shaky semantics... can you guess which gets declared first, the foo symbol or the x symbol? And what exactly is this? A statement? An expression? Both? Two statements? yikes!

I'm not asking you to put that feature in CoffeeScript.

But function x() {} is a useful statement that I strongly prefer over introducing variables and closures, e.g. var x; x = function() {}, which I believe is the recommended work-around. But in this case, I'm not declaring a variable - I'm declaring a function. Yes, the resulting function occupies a symbol/variable, but the semantics are different. The work-around code reads "declare a variable. assign an anonymous function to the variable." - while the statement-like form reads "declare a function", which is what I'm doing.

I know you want everything to be an expression, but not every function is an expression - sometimes a function is just a function.

How about introducing a statement and keyword for named functions? e.g.:

fn countdown (n)
    if n > 0
        recursive(n-1)

countdown 10

fn (or fun or func, whatever) would be a statement, not an expression, and would compile into function countdown(n) { ... } - a statement, in the sense of if, for or while etc. which you can't use as expressions.

Also this improves upon the confusing semantics of JavaScript where function countdown() {} is both a statement and an expression, since the var x = function y() {} form would not be possible.

Thoughts?

@mindplay-dk
Copy link

For that matter, named functions declared as functions (rather than variables) should probably be immutable - so countdown = x should throw an error. That way, in CoffeeScript, an actual function can be thought of as an actual function, rather than just another local variable that you can overwrite at will - that might make literal functions a little more "bulletproof" than their anonymous cousins.

@mindplay-dk
Copy link

Note the description of semantics in the ECMA-262 (5.1) Function Specification which defines two different productions, FunctionDeclaration and FunctionExpression, which have identical syntax, but different semantics.

FunctionDeclaration and FunctionExpression with no Identifier are both straightforward, but notice the lengthy description of FunctionExpression with the Identifier - no wonder the language vendors get this one wrong ;-)

@epidemian
Copy link
Contributor

Mmm... i think that, as long as we're supporting IE <= 8, there's no other reasonable option than what the current implementation does.

Introducing a new syntax to generate JS function declarations, thus increasing the language's complexity by having yet another way of defining functions and adding all the function hoisting nonsense to CoffeeScript, just for better stack traces is a no-go IMHO.

Compiling all functions to named function expressions (or, at least, all functions assigned to a variable) would be a reasonable alternative, if it weren't for stupid IE <= 8 behaviour, which clearly violates the spec that @mindplay-dk linked:

NOTE: The Identifier in a FunctionExpression can be referenced from inside the FunctionExpression's FunctionBody to allow the function to call itself recursively. However, unlike in a FunctionDeclaration, the Identifier in a FunctionExpression cannot be referenced from and does not affect the scope enclosing the FunctionExpression.

So, unless i'm interpreting things the wrong way here, i think we're stuck with current semantics unless we decide to ignore IE <= 8.

Are the better stack traces the only advantage of having named functions?

@michaelficarra
Copy link
Collaborator

See michaelficarra/CoffeeScriptRedux@45c5d56 and the related discussion for ways to work around that IE<9 bug.

@rlidwka
Copy link

rlidwka commented Oct 12, 2013

The following form is bad:
var x = function foo() {
};

I used var x = function x() {} for ages and never had any issues with that. Statements like module.exports.func = function func(){} are often seen in npm packages. This ain't bad.

i think that, as long as we're supporting IE <= 8, there's no other reasonable option than what the current implementation does.

We can introduce "--fuck-ie8" compiler option (which I would very much use as a default because I develop for node/v8 only and don't see any reason why IE should have any impact whatsoever on syntax of my node.js code)

Are the better stack traces the only advantage of having named functions?

I think so. It is a reason enough though.

@mindplay-dk
Copy link

I used var x = function x() {} for ages and never had any issues with that.

What's the point? How is that different from function x() {}?

Statements like module.exports.func = function func(){} are often seen in npm packages. This ain't bad.

Not bad, but it does clutter the local scope with what is most likely unused variables - unless you do it inside throw-away one-shot closures as suggested by @michaelficarra above. Which is also not bad, it's just... odd...

Sticking to "JavaScript, the good parts" usually means playing avoidance, and the odd/complicated semantics of the contrived function-declaration-expression doesn't strike me as one of the good parts.

I don't know, I'm probably just obsessing about details as usual...

@erisdev
Copy link

erisdev commented Oct 12, 2013

@mindplay-dk cluttering the what now? Not so. The name in an NFE is just the function's name property and nothing more. At least, it's supposed to be.

> x = function y() { }
[Function: y]
> x
[Function: y]
> y
ReferenceError: y is not defined

@epidemian
Copy link
Contributor

@mindplay-dk

I used var x = function x() {} for ages and never had any issues with that.

What's the point? How is that different from function x() {}?

It's different in that it doesn't hoist the function definition up into the enclosing scope:

console.log(x); // -> undefined
var x = function x() {};

At least, that the expected behaviour (which IE <= 8 breaks).


@rlidwka

Are the better stack traces the only advantage of having named functions?

I think so. It is a reason enough though.

Yeah, it is a very valid practical reason. I was just curious to know if there were other, more fundamental reasons, why using only anonymous functions was wrong.


@michaelficarra

Oh, i had totally forgotten about that discussion. Yeah, so another alternative, that would allow us to use named function expressions and not screw up on IE <= 8, would be generating a fresh name for the function expression. I'm not sure if the current compiler is up for that hehe.

Still, i'd really like to see practical example of when stack traces could be improved by using named functions.

The OP shows an example of using a named function as a callback for an asynchronous operation, but a normal variable can be used if we want to have the function name in the stack trace:

$('button').on 'click', myClickHandler = -> 
  throw new Error 'boom'
# When clicking the button (on Chrome dev tools):
# Uncaught Error: boom
#   $.on.myClickHandler
#   jQuery.event.dispatch
#   elemData.handle

(jsFiddle)

@pflannery
Copy link

I agree with the poster, we need a way for named closures, not just for stack analysis but also for heap profile analysis. This image is taken from google
image

citation source (see below the containment image for the tip)

Maybe a no-space op like

    namedClosure->
        x = 10
        y = 1
        return

or

   ->namedClosure
        x = 10
        y = 1
        return

@klippx
Copy link

klippx commented Apr 3, 2014

I would say there will be a quite a huge need for NFE now; http://www.html5rocks.com/en/tutorials/developertools/async-call-stack/#disqus_thread

@pke
Copy link

pke commented Apr 3, 2014

Sure, IE had this for some time now and it's really annoying that CS doesn't support named anon functions.

@carlsmith
Copy link
Contributor

So, unless i'm interpreting things the wrong way here, i think we're stuck with current semantics unless we decide to ignore IE <= 8.

Are the better stack traces the only advantage of having named functions?

There's another advantage that hasn't been mentioned: When pretty printing CoffeeScript objects, it's nice to have the names of the functions, like [thisFunc, thatFunc], instead of [function, function].

@tobia
Copy link

tobia commented Jul 3, 2014

try it. The last time I checked in Chrome, the debugger is able to infer the name of every function

It does indeed. In case other people are wondering what @jashkenas is referring to, this is the syntax to use:

setTimeout myCallback = ->
    throw 'NOES'
, 100

instead of the simpler:

setTimeout ->
    throw 'NOES'
, 100

Results:

screen shot

For those browsers or dev tools that don't infer the name, CS could just recognize such an assignment of a lambda to a name (or to a constant object property, while we're at it) and give the same name to the JS function definition.

This would solve the problem for 99% of use cases while not changing a comma of CS syntax.

@pke
Copy link

pke commented Jul 3, 2014

We put that in a fork a year ago, I am surprised this is still debated here.

@forty
Copy link

forty commented Aug 28, 2014

Here a non debug scenario where it could be useful:

EventEmitter = (require('events')).EventEmitter;

var e1 = new EventEmitter();

e1.on('event', function onEvent() {
  console.log('e1 event!');
  return e1.removeListener('event', onEvent);
});

e1.emit('event');
e1.emit('event');

Of course it's not too complicated to put it in a var, but I find it less elegant in this case.

@epidemian
Copy link
Contributor

@forty, why do you find it less elegant? I think it's just the same:

{EventEmitter} =  require 'events'

e1 = new EventEmitter

e1.on 'event', onEvent = -> 
  console.log 'e1 event!'
  e1.removeListener 'event', onEvent

e1.emit 'event'
e1.emit 'event'

The only difference being that the onEvent variable is leaked onto the outer scope instead of just living inside its function body.

Off-topic: even in this little example, the difference in punctuation noise between JS and Coffee is quite stark! I really like that 😸

@rlidwka
Copy link

rlidwka commented Aug 29, 2014

even in this little example, the difference in punctuation noise between JS and Coffee is quite stark!

Not so much. This is the javascript for you:

var {EventEmitter} = require('events')

var e1 = new EventEmitter

e1.on('event', () => {
  console.log('e1 event!')
  e1.removeListener('event', arguments.callee)
})

e1.emit('emit')
e1.emit('emit')

Once people learn to get rid of those noisy and pointless semicolons and v8 implements proper es6, js code gets much cleaner.

@forty
Copy link

forty commented Aug 29, 2014

@epidemian I did not though of using an assignment as an argument, I like it in this case.

Note that the code in my previous comment was originally coffee output so it's probably a bit more verbose than hand written javascript.

Thanks for you answers!

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
Projects
None yet
Development

No branches or pull requests