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

Named function declaration syntax #1640

Closed
mourner opened this Issue Aug 30, 2011 · 25 comments

Comments

Projects
None yet
@mourner

mourner commented Aug 30, 2011

I know that this issue is mentioned in the FAQ and there's a reason not to use named functions because of IE bugs, but as I understood it only applies when you use both a named function and a function expression at the same time, e.g. f = function g() { ... }, and if you just need to do function g() { ... }, there's no problem with this.

So, considering this, is there any way you could enable some named functions syntax in CoffeeScript in the future? I figured out that I need this because it turns out that Closure Compiler inlines named functions quite aggressively, while it never inlines function expressions assigned to a variable, so the CoffeeScript equivalent in my case produces a 20% bigger compressed output. Inlining also provides performance benefits.

@michaelficarra

This comment has been minimized.

Show comment
Hide comment
@michaelficarra

michaelficarra Aug 31, 2011

Collaborator

What's the use case?

Collaborator

michaelficarra commented Aug 31, 2011

What's the use case?

@benekastah

This comment has been minimized.

Show comment
Hide comment
@benekastah

benekastah Aug 31, 2011

It can be helpful to organize your code a little more nicely, since named functions are accessible anywhere in the file rather than after they are declared only. When coding vanilla javascript, I sometimes like to put my private functions below all the public stuff, for example.

benekastah commented Aug 31, 2011

It can be helpful to organize your code a little more nicely, since named functions are accessible anywhere in the file rather than after they are declared only. When coding vanilla javascript, I sometimes like to put my private functions below all the public stuff, for example.

@michaelficarra

This comment has been minimized.

Show comment
Hide comment
@michaelficarra

michaelficarra Aug 31, 2011

Collaborator

So you also want the hoisting semantics? I don't think that's a good idea. If I see a name in a section of your code, and it hasn't been defined above, I am going to assume it is global, not scan the rest of the file for a function declaration. I think that's a wart of javascript. I never, ever use FDs.

Collaborator

michaelficarra commented Aug 31, 2011

So you also want the hoisting semantics? I don't think that's a good idea. If I see a name in a section of your code, and it hasn't been defined above, I am going to assume it is global, not scan the rest of the file for a function declaration. I think that's a wart of javascript. I never, ever use FDs.

@benekastah

This comment has been minimized.

Show comment
Hide comment
@benekastah

benekastah Aug 31, 2011

That's a really good point. I hadn't thought of that.

benekastah commented Aug 31, 2011

That's a really good point. I hadn't thought of that.

@weepy

This comment has been minimized.

Show comment
Hide comment
@weepy

weepy Aug 31, 2011

another advantage of named functions is that they automatically have a "name", i.e.

function hello() { } 
hello.name == "hello"

however I understand it's problematic in IE to have named functions equalling other variables.

weepy commented Aug 31, 2011

another advantage of named functions is that they automatically have a "name", i.e.

function hello() { } 
hello.name == "hello"

however I understand it's problematic in IE to have named functions equalling other variables.

@jashkenas

This comment has been minimized.

Show comment
Hide comment
@jashkenas

jashkenas Aug 31, 2011

Owner

Yes, despite the IE bugs with respect to named functions, they're still a JavaScript wart that we would want to do our best to avoid anyway. There shouldn't be three different ways to create a function, each with slightly different semantics: function declarations, function expressions, and named function expressions.

In CoffeeScript, there are just function expressions to worry about, without any hoisting headaches, like this one:

if (false) {
  function Func(){};
  window.Func2 = Func;
}

alert(Func);
alert(Func2);

Finally, modern JS engines are good about providing names in stacktraces for regular function expressions. http://cl.ly/9WAw

Owner

jashkenas commented Aug 31, 2011

Yes, despite the IE bugs with respect to named functions, they're still a JavaScript wart that we would want to do our best to avoid anyway. There shouldn't be three different ways to create a function, each with slightly different semantics: function declarations, function expressions, and named function expressions.

In CoffeeScript, there are just function expressions to worry about, without any hoisting headaches, like this one:

if (false) {
  function Func(){};
  window.Func2 = Func;
}

alert(Func);
alert(Func2);

Finally, modern JS engines are good about providing names in stacktraces for regular function expressions. http://cl.ly/9WAw

@jashkenas jashkenas closed this Aug 31, 2011

@mourner

This comment has been minimized.

Show comment
Hide comment
@mourner

mourner Aug 31, 2011

OK, thanks for clarifications!

After further research it seems that Closure Compiler doesn't inline the function because var declaration and function assignment are not in the same place, so Closure Compiler thinks that these functions can't be safely inlined. So Closure Compiler NEVER inlines functions in CoffeeScript-generated code.

mourner commented Aug 31, 2011

OK, thanks for clarifications!

After further research it seems that Closure Compiler doesn't inline the function because var declaration and function assignment are not in the same place, so Closure Compiler thinks that these functions can't be safely inlined. So Closure Compiler NEVER inlines functions in CoffeeScript-generated code.

@jashkenas

This comment has been minimized.

Show comment
Hide comment
@jashkenas

jashkenas Aug 31, 2011

Owner

Ugh. That's something we should probably follow up with the Closure Compiler team, by opening a bug report.

It shouldn't make a difference where the var happens to be.

Owner

jashkenas commented Aug 31, 2011

Ugh. That's something we should probably follow up with the Closure Compiler team, by opening a bug report.

It shouldn't make a difference where the var happens to be.

@mourner

This comment has been minimized.

Show comment
Hide comment
@mourner

mourner Aug 31, 2011

Yep, submitted an issue to the Closure Compiler bugtracker: http://code.google.com/p/closure-compiler/issues/detail?id=546

mourner commented Aug 31, 2011

Yep, submitted an issue to the Closure Compiler bugtracker: http://code.google.com/p/closure-compiler/issues/detail?id=546

@togakangaroo

This comment has been minimized.

Show comment
Hide comment
@togakangaroo

togakangaroo Apr 14, 2013

Hoping this decision would get re-evaluated but not really expecting to see that happen

If I see a name in a section of your code, and it hasn't been defined above, I am going to assume it is global, not scan the rest of the file for a function declaration.

With all due respect, that is a silly reason, especially when file-length is capped at a couple hundred lines. I regularly put simple file-specific helpers in named functions at the bottom of the file. @michaelficarra - are you really saying that if you encounter something like stripNonImportantAddressCharacters(addressfield) in an otherwise well-organized codebase using requirejs and all that good stuff, that you're going to look globally rather than scan the file? More importantly, are you going to stop reading right there and then, just because something looks out of place?

The big use case here is for those people (me) who believe that the core of what a module does in domain terms should be the first thing you see when opening a file. Named functions are really useful for bumping helpers, callbacks, and wrappers away from that so when someone first opens a file they can get a quick overview of what it does before diving into details.

togakangaroo commented Apr 14, 2013

Hoping this decision would get re-evaluated but not really expecting to see that happen

If I see a name in a section of your code, and it hasn't been defined above, I am going to assume it is global, not scan the rest of the file for a function declaration.

With all due respect, that is a silly reason, especially when file-length is capped at a couple hundred lines. I regularly put simple file-specific helpers in named functions at the bottom of the file. @michaelficarra - are you really saying that if you encounter something like stripNonImportantAddressCharacters(addressfield) in an otherwise well-organized codebase using requirejs and all that good stuff, that you're going to look globally rather than scan the file? More importantly, are you going to stop reading right there and then, just because something looks out of place?

The big use case here is for those people (me) who believe that the core of what a module does in domain terms should be the first thing you see when opening a file. Named functions are really useful for bumping helpers, callbacks, and wrappers away from that so when someone first opens a file they can get a quick overview of what it does before diving into details.

@michaelficarra

This comment has been minimized.

Show comment
Hide comment
@michaelficarra

michaelficarra Apr 15, 2013

Collaborator

This is a long-dead thread, but I'll bite:

are you going to stop reading right there and then, just because something looks out of place?

Yes. Because I can't possibly understand it. When you read a technical publication, are the terms used within it defined before or after the section of the paper that covers the main topic? You can skip ahead to that section and hope to understand the terms from the context in which they're used, but the sane approach is to learn what they really mean first.

Collaborator

michaelficarra commented Apr 15, 2013

This is a long-dead thread, but I'll bite:

are you going to stop reading right there and then, just because something looks out of place?

Yes. Because I can't possibly understand it. When you read a technical publication, are the terms used within it defined before or after the section of the paper that covers the main topic? You can skip ahead to that section and hope to understand the terms from the context in which they're used, but the sane approach is to learn what they really mean first.

@xixixao

This comment has been minimized.

Show comment
Hide comment
@xixixao

xixixao Apr 21, 2013

Contributor

@michaelficarra I have to say this is a number 1 issue for me in CoffeeScript (and it has been my number one language for the past year or so). This approach (shared by jashkenas) that things should be defined first NEVER applies to programming - unless you have one C or CoffeeScript source file. In every other case, you abstract away, by including definitions from libraries, other files and "gluing" them together. For anyone who at least tried coding in Haskell (and I hope that is generally accepted as a role-model in many aspects), this is a no-brainer. Having hoisted function definitions is actually deeply tied in with functional, top-down approach to programming. Which is something I hope has enough traction nowadays I don't need to advocate it.

I generally solve this in two ways, neither of them being ideal.

start = ->
  funA "input"

funA = (input) ->
  funB input

funB = (input) -> #...

start()

Well, this is almost like requiring a main function, yuck.

class Module
  constructor: ->
    @funA "input"

  @funA: (input) ->
    @funB input

  @funB: (input) -> #...
# someone else calls new Module()

Loads of @s, motivates towards verbosity.

For comparison, an excerpt from Haskell:

compile program =
  [Jsr "main",  Halt] ++  transProgram program

transProgram defs =
  concatMap transFunction defs

transFunction = -- ...

This is basically applying the same abstraction principles we use every day between files to the scope of one file. This way, you immediately see the big picture, and then you can continue on reading the file, to looking up the things that you're interested in (note that you see "the menu" first, things to choose from, instead of reading through everything without knowing how it's being used).

Your analogy is absolutely perfect. In a good technical book, the terms (in Haskell types, in JavaScript constants or comments) are explained first, then the author proceeds with an overview, then he goes into more details on each topic and then he includes "advanced" chapters with really down-to-metal details. IMO.

That's mainly reiterating what @togakangaroo already expressed. More importantly, unfortunately, I don't see a way to support this without additional keyword, and that would really suck.

Contributor

xixixao commented Apr 21, 2013

@michaelficarra I have to say this is a number 1 issue for me in CoffeeScript (and it has been my number one language for the past year or so). This approach (shared by jashkenas) that things should be defined first NEVER applies to programming - unless you have one C or CoffeeScript source file. In every other case, you abstract away, by including definitions from libraries, other files and "gluing" them together. For anyone who at least tried coding in Haskell (and I hope that is generally accepted as a role-model in many aspects), this is a no-brainer. Having hoisted function definitions is actually deeply tied in with functional, top-down approach to programming. Which is something I hope has enough traction nowadays I don't need to advocate it.

I generally solve this in two ways, neither of them being ideal.

start = ->
  funA "input"

funA = (input) ->
  funB input

funB = (input) -> #...

start()

Well, this is almost like requiring a main function, yuck.

class Module
  constructor: ->
    @funA "input"

  @funA: (input) ->
    @funB input

  @funB: (input) -> #...
# someone else calls new Module()

Loads of @s, motivates towards verbosity.

For comparison, an excerpt from Haskell:

compile program =
  [Jsr "main",  Halt] ++  transProgram program

transProgram defs =
  concatMap transFunction defs

transFunction = -- ...

This is basically applying the same abstraction principles we use every day between files to the scope of one file. This way, you immediately see the big picture, and then you can continue on reading the file, to looking up the things that you're interested in (note that you see "the menu" first, things to choose from, instead of reading through everything without knowing how it's being used).

Your analogy is absolutely perfect. In a good technical book, the terms (in Haskell types, in JavaScript constants or comments) are explained first, then the author proceeds with an overview, then he goes into more details on each topic and then he includes "advanced" chapters with really down-to-metal details. IMO.

That's mainly reiterating what @togakangaroo already expressed. More importantly, unfortunately, I don't see a way to support this without additional keyword, and that would really suck.

@togakangaroo

This comment has been minimized.

Show comment
Hide comment
@togakangaroo

togakangaroo Apr 21, 2013

I was going to respond, got sidetracked with a hack-a-thon and forgot.

Glad to see there's other people that share my point of view. In reality I've seen almost no developers other than myself using function hoisting on purpose. I certainly understand why you might want to eliminate the paradigm. Douglas Crockford argues that "if a feature is more often misused then it's a bug", I don't buy into this argument, but I respect it as a valid point of view. In addition I realize I'm an outsider coming to an issue that's likely been discussed to death. I get that and feel free to ignore me. However, I love the way cofeescript writes beautiful code, and I feel like this feature would allow you to make code even more beautiful.

As @xixixao mentioned, the first thing you hope to see in a paper is the summary or abstract that lets you know what it is about (actually the first thing is the title and authors which in the case of code you already got from the file name and project pages). This is what beautiful code should look like. Just because you found it useful to refactor a helper getFullAddress method in the script, does not mean I want to see its implementation before I even know what the script is about.

The problem with @michaelficarra's analogy is that function names are not like glossary terms, they are like glossary definitions. Glossaries come first because when a reader sees the term "metastasis" they must have at least some idea of what it means in your context for the sentence to make sense. getFullAddress, calculateMean, unwrapParameters - these are not like that. A function name (and proper naming is way more important than any code structure discussion) will already provide this. In the analogy, the function body is closer to a full dictionary definition of the term, complete with pronunciation and grammatical information. As such, it is more akin to a footnote.

As for how to implement it, I think a new keyword might not be that bad

    hoist getFullAdress = (x) -> ....

Even better and more backwards-compatible would be a symbol that is not valid as the first character of a variable.

    :getFullAdress = (x) -> ....

would work nicely.

togakangaroo commented Apr 21, 2013

I was going to respond, got sidetracked with a hack-a-thon and forgot.

Glad to see there's other people that share my point of view. In reality I've seen almost no developers other than myself using function hoisting on purpose. I certainly understand why you might want to eliminate the paradigm. Douglas Crockford argues that "if a feature is more often misused then it's a bug", I don't buy into this argument, but I respect it as a valid point of view. In addition I realize I'm an outsider coming to an issue that's likely been discussed to death. I get that and feel free to ignore me. However, I love the way cofeescript writes beautiful code, and I feel like this feature would allow you to make code even more beautiful.

As @xixixao mentioned, the first thing you hope to see in a paper is the summary or abstract that lets you know what it is about (actually the first thing is the title and authors which in the case of code you already got from the file name and project pages). This is what beautiful code should look like. Just because you found it useful to refactor a helper getFullAddress method in the script, does not mean I want to see its implementation before I even know what the script is about.

The problem with @michaelficarra's analogy is that function names are not like glossary terms, they are like glossary definitions. Glossaries come first because when a reader sees the term "metastasis" they must have at least some idea of what it means in your context for the sentence to make sense. getFullAddress, calculateMean, unwrapParameters - these are not like that. A function name (and proper naming is way more important than any code structure discussion) will already provide this. In the analogy, the function body is closer to a full dictionary definition of the term, complete with pronunciation and grammatical information. As such, it is more akin to a footnote.

As for how to implement it, I think a new keyword might not be that bad

    hoist getFullAdress = (x) -> ....

Even better and more backwards-compatible would be a symbol that is not valid as the first character of a variable.

    :getFullAdress = (x) -> ....

would work nicely.

@epidemian

This comment has been minimized.

Show comment
Hide comment
@epidemian

epidemian Apr 21, 2013

Contributor

I think the pattern of declaring the functions of higher level (or more abstract) first and then the auxiliary, or lower level/more concrete, ones (described by @xixixao and other here) is very common. But i don't see how CS semantics for function declarations (and any kind of variables really) prevents that; in fact i recall having done that quite often. For example:

exports.drawSquare = (x, y, size) ->
  drawLine x, y, x + size, y
  drawLine x + size, y, x + size, y + size
  # ...
drawLine = (x1, y1, x2, y2) ->
  # ...

Will work just fine.

You can't call a function before it has been defined; but you can reference it just fine if it's then gonna be declared in an enclosing scope :)


I think hoisting function declarations so they are usable (i.e. callable) before they have been defined is a bad idea. Mostly, because it would mean that variables that contain functions would behave differently to other variables.

For example, if i had this thing working because of top-level function hoisting:

console.log "gravity is #{gravity()}"
# ...
gravity = -> 9.8

And then i realize that it doesn't make sense that gravity is a computed value; it could just be a number. But if i would make that simple refactor, my code would break because variables that contain numbers don't hoist:

console.log "gravity is #{gravity}" # Boom!
# ...
gravity = 9.8
Contributor

epidemian commented Apr 21, 2013

I think the pattern of declaring the functions of higher level (or more abstract) first and then the auxiliary, or lower level/more concrete, ones (described by @xixixao and other here) is very common. But i don't see how CS semantics for function declarations (and any kind of variables really) prevents that; in fact i recall having done that quite often. For example:

exports.drawSquare = (x, y, size) ->
  drawLine x, y, x + size, y
  drawLine x + size, y, x + size, y + size
  # ...
drawLine = (x1, y1, x2, y2) ->
  # ...

Will work just fine.

You can't call a function before it has been defined; but you can reference it just fine if it's then gonna be declared in an enclosing scope :)


I think hoisting function declarations so they are usable (i.e. callable) before they have been defined is a bad idea. Mostly, because it would mean that variables that contain functions would behave differently to other variables.

For example, if i had this thing working because of top-level function hoisting:

console.log "gravity is #{gravity()}"
# ...
gravity = -> 9.8

And then i realize that it doesn't make sense that gravity is a computed value; it could just be a number. But if i would make that simple refactor, my code would break because variables that contain numbers don't hoist:

console.log "gravity is #{gravity}" # Boom!
# ...
gravity = 9.8
@epidemian

This comment has been minimized.

Show comment
Hide comment
@epidemian

epidemian Apr 21, 2013

Contributor

@togakangaroo (awesome nickname BTW 😆), i posted my comment before reading yours. I fully agree with your rationale for why the key concepts should appear first in the source code and then the secondary/helper stuff. Yet i don't see why function hoisting has anything to do with this. The CS compiler itself follows that premise of putting the helper stuff at the bottom of the files (take a look at optparse.coffee or lexer.coffee for example) and of course it doesn't rely on function hoisting (notice that some of the helper variables are not functions, but strings/rgexes/numbers too).

Contributor

epidemian commented Apr 21, 2013

@togakangaroo (awesome nickname BTW 😆), i posted my comment before reading yours. I fully agree with your rationale for why the key concepts should appear first in the source code and then the secondary/helper stuff. Yet i don't see why function hoisting has anything to do with this. The CS compiler itself follows that premise of putting the helper stuff at the bottom of the files (take a look at optparse.coffee or lexer.coffee for example) and of course it doesn't rely on function hoisting (notice that some of the helper variables are not functions, but strings/rgexes/numbers too).

@togakangaroo

This comment has been minimized.

Show comment
Hide comment
@togakangaroo

togakangaroo Apr 21, 2013

Thanks @epidemian. Your example (and the ones you link to) assumes node js. If your entry point is a callback (as with module loaders) then there is no problem.

Consider however javascript that is tied to execution order (as in web or a simple node script). To get the same behavior you would have to do something like this:

start = ->
       nextBoard = copyBoard currentBoard
       apply (overcrowdedCells currentBoard), nextBoard 
       apply (bornCells currentBoard), nextBoard 
       currentBoard = nextBoard 
....

start()

This is certainly not the end of the world but you notice that it requires two extra lines and an extra level of indentation. It's just not particularly beautiful and is an extra pattern to enforce.

I think you bring up a valid issue but I don't know if it's really that big a deal, after all in your example you had to change the way gravity is used everywhere already.My proposal is for a declaration/assignment form which would allow only functions on the right-hand side.

:overcrowdedCells = (board) -> ...

or

overcrowdedCels := (board) ->

An even better (but more difficult to implement) approach would be for this to actually be a semantic for "this value is function scope and immutable" in which case it would fall to the compiler to do the validation and hoisting. This would be nice as it would give us a feature (immutables) which I really wish javascript had.

togakangaroo commented Apr 21, 2013

Thanks @epidemian. Your example (and the ones you link to) assumes node js. If your entry point is a callback (as with module loaders) then there is no problem.

Consider however javascript that is tied to execution order (as in web or a simple node script). To get the same behavior you would have to do something like this:

start = ->
       nextBoard = copyBoard currentBoard
       apply (overcrowdedCells currentBoard), nextBoard 
       apply (bornCells currentBoard), nextBoard 
       currentBoard = nextBoard 
....

start()

This is certainly not the end of the world but you notice that it requires two extra lines and an extra level of indentation. It's just not particularly beautiful and is an extra pattern to enforce.

I think you bring up a valid issue but I don't know if it's really that big a deal, after all in your example you had to change the way gravity is used everywhere already.My proposal is for a declaration/assignment form which would allow only functions on the right-hand side.

:overcrowdedCells = (board) -> ...

or

overcrowdedCels := (board) ->

An even better (but more difficult to implement) approach would be for this to actually be a semantic for "this value is function scope and immutable" in which case it would fall to the compiler to do the validation and hoisting. This would be nice as it would give us a feature (immutables) which I really wish javascript had.

@epidemian

This comment has been minimized.

Show comment
Hide comment
@epidemian

epidemian Apr 22, 2013

Contributor

Your example (and the ones you link to) assumes node js. If your entry point is a callback (as with module loaders) then there is no problem.

@togakangaroo Yeah, but the the CommonJS module pattern was not the main point. When doing client-side JS/CS without a module system you can also program the same way (take out the exports. from my example and it should still work the same; you can put it on a .js file and then <script> it in a page). As an example, you can again take the CS compiler; yes, it uses a CommonJS-like module system, but when it compiles for browsers it just concatenates all the files together.

The main point is: when you're declaring functions, you can reference things that haven't been initialized inside those functions. As long as those variables are not evaluated before they have been initialized, there won't be any problems. But when you want to run some stuff, yes, you do need that everything you will use have been initialized, which sounds like a very reasonable requirement to me =P

I think you bring up a valid issue but I don't know if it's really that big a deal

Hmm, i disagree. If the main argument in favour of this proposal is to be able to declare functions in any order we want, then the fact that we can already do that without any hassle is quite a big deal for the proposed feature, as it renders it unnecessary.

after all in your example you had to change the way gravity is used everywhere already.

Yes. But that's a change i would expect to have to do if i'm changing the type of an object; it's a simple find and replace gravity() to gravity. I would not expect, however, the scoping rules of that variable to change just because i changed what it references.

Same argument could apply, for example, to decorator functions. Imagine that we have this hoisting rules for functions and the code:

alert "result: #{veryExpensiveComputation()}" # Works because of hoisting.

veryExpensiveComputation = ->
  # ...

And then i want to profile that very expensive function, so i decorate it with a profile function.

alert "result: #{veryExpensiveComputation()}" # No longer works :(

veryExpensiveComputation = profile ->
  # ...

I think it would be very unintuitive that this would break all of a sudden just because i decorated a function.

Contributor

epidemian commented Apr 22, 2013

Your example (and the ones you link to) assumes node js. If your entry point is a callback (as with module loaders) then there is no problem.

@togakangaroo Yeah, but the the CommonJS module pattern was not the main point. When doing client-side JS/CS without a module system you can also program the same way (take out the exports. from my example and it should still work the same; you can put it on a .js file and then <script> it in a page). As an example, you can again take the CS compiler; yes, it uses a CommonJS-like module system, but when it compiles for browsers it just concatenates all the files together.

The main point is: when you're declaring functions, you can reference things that haven't been initialized inside those functions. As long as those variables are not evaluated before they have been initialized, there won't be any problems. But when you want to run some stuff, yes, you do need that everything you will use have been initialized, which sounds like a very reasonable requirement to me =P

I think you bring up a valid issue but I don't know if it's really that big a deal

Hmm, i disagree. If the main argument in favour of this proposal is to be able to declare functions in any order we want, then the fact that we can already do that without any hassle is quite a big deal for the proposed feature, as it renders it unnecessary.

after all in your example you had to change the way gravity is used everywhere already.

Yes. But that's a change i would expect to have to do if i'm changing the type of an object; it's a simple find and replace gravity() to gravity. I would not expect, however, the scoping rules of that variable to change just because i changed what it references.

Same argument could apply, for example, to decorator functions. Imagine that we have this hoisting rules for functions and the code:

alert "result: #{veryExpensiveComputation()}" # Works because of hoisting.

veryExpensiveComputation = ->
  # ...

And then i want to profile that very expensive function, so i decorate it with a profile function.

alert "result: #{veryExpensiveComputation()}" # No longer works :(

veryExpensiveComputation = profile ->
  # ...

I think it would be very unintuitive that this would break all of a sudden just because i decorated a function.

@jiangmiao

This comment has been minimized.

Show comment
Hide comment
@jiangmiao

jiangmiao Apr 22, 2013

Contributor

@epidemian Change function declaration to variable assignment will have same issue in JavaScript.

Contributor

jiangmiao commented Apr 22, 2013

@epidemian Change function declaration to variable assignment will have same issue in JavaScript.

@xixixao

This comment has been minimized.

Show comment
Hide comment
@xixixao

xixixao Apr 22, 2013

Contributor

@epidemian It probably isn't obvious at all from the examples I gave, but this not just about the top scope level.

product = (array) ->
  array.reduce times, 1
  times = (x, y) ->
    x * y

(imagine those are more complicated) As I hinted, the two currently possible work-arounds only allow you to write "Java-like" code.

I agree with your objections against transparency, they are the exact reason why there would HAVE to be a new syntax for hoisted function definitions.

Contributor

xixixao commented Apr 22, 2013

@epidemian It probably isn't obvious at all from the examples I gave, but this not just about the top scope level.

product = (array) ->
  array.reduce times, 1
  times = (x, y) ->
    x * y

(imagine those are more complicated) As I hinted, the two currently possible work-arounds only allow you to write "Java-like" code.

I agree with your objections against transparency, they are the exact reason why there would HAVE to be a new syntax for hoisted function definitions.

@epidemian

This comment has been minimized.

Show comment
Hide comment
@epidemian

epidemian Apr 22, 2013

Contributor

@xixixao Hmmm... given your example, it occurred to me that a where block could fit for these purposes:

product = (array) ->
  array.reduce times, 1
  where 
    times = (x, y) -> x * y

There could be more than one thing defined in a where block:

composeMany = (fns) ->
  fns.reduce compose, id
  where
    id = (x) -> x
    compose = (f, g) -> (x) -> f g x

(didn't check if the order of composition is right in that example)

Though it is unclear to me if hoisting the functions inside the where would be the best thing to do. It would prevent things other than functions from being defined in a where block for example.

Of course, the where block is not a new idea. Some language that i shall not mention uses it quite extensively, and it's also implemented in LiveScript it seems (though it has been deprecated for some reason, maybe @gkz can shed some light).

Contributor

epidemian commented Apr 22, 2013

@xixixao Hmmm... given your example, it occurred to me that a where block could fit for these purposes:

product = (array) ->
  array.reduce times, 1
  where 
    times = (x, y) -> x * y

There could be more than one thing defined in a where block:

composeMany = (fns) ->
  fns.reduce compose, id
  where
    id = (x) -> x
    compose = (f, g) -> (x) -> f g x

(didn't check if the order of composition is right in that example)

Though it is unclear to me if hoisting the functions inside the where would be the best thing to do. It would prevent things other than functions from being defined in a where block for example.

Of course, the where block is not a new idea. Some language that i shall not mention uses it quite extensively, and it's also implemented in LiveScript it seems (though it has been deprecated for some reason, maybe @gkz can shed some light).

@vendethiel

This comment has been minimized.

Show comment
Hide comment
@vendethiel

vendethiel Apr 22, 2013

Collaborator

(though it has been deprecated for some reason, maybe @gkz can shed some light).

Not used. It's really not that interesting with let (and =>). Plus this is currently valid syntax (maybe used for query DSLs)

Collaborator

vendethiel commented Apr 22, 2013

(though it has been deprecated for some reason, maybe @gkz can shed some light).

Not used. It's really not that interesting with let (and =>). Plus this is currently valid syntax (maybe used for query DSLs)

@togakangaroo

This comment has been minimized.

Show comment
Hide comment
@togakangaroo

togakangaroo Apr 23, 2013

@epidemian I understand what you're saying about it being referenced vs evaluated. What I'm saying is that in the case where you're not using a package manager, you generally want to start evaluating at least some things.

So consider your example, but instead of this being a function that will draw a square on demand, you actually want to draw a square now. So you have something like what I proposed with the start method:

drawSquare = (x, y, size) ->
  drawLine x, y, x + size, y
  drawLine x + size, y, x + size, y + size

drawLine = (x1, y1, x2, y2) ->
  #...
drawSquare 10, 10, 100

Which as we're saying, is not ideal as it hides the central thing that your file is doing at the bottom of the file. This gets a lot worse if you want to do something more complex - like fire an ajax request to fetch the number of users and then draw a square for each of them, and maybe place info in each square. You end up with either all this code at the bottom, or in a start method at the very top. Quite icky.

My assertion is that this is not a fringe minority case, few enough people use module loaders that this is incredibly common, and javascript, for all its faults, used hoisting as a reasonable solution to the issue. Hoisting was abused to high-heaven, sure, but when used intentionally (as I'm suggesting coffeescript allow with a new assignment operator) it makes for much nicer code.

As for it being misleading when changing variables to functions, that is a fair point. It's not a huge issue to my style of coding but I could see that bothering some people more. I still think that my proposal comes with enough warning-bells. Suppose you went with my syntax proposal of a new symbol. Let's say something like this (I'm going to change it to caret cause I like ^= better than :=):

gravity ^= -> 9.8

In my proposal if you simply removed the function in favor of a scalar, this would not compile therefore giving you an extra indicator of something being fundamentally wrong.

You do make a good point about function decorators, this operator would have to take them into account and implement

veryExpensiveComputation ^= profile -> ...

as

function veryExpensiveComputation() {
  return profile.apply(this, function(){
    ...
  });
}

Not great, but not terribly difficult to implement either. My biggest preference is that the coffeescript compiler itself would do the hoisting, which would allow us to keep using function expressions and get rid of that mess above, but I have not convinced myself yet how good an idea that would be.

togakangaroo commented Apr 23, 2013

@epidemian I understand what you're saying about it being referenced vs evaluated. What I'm saying is that in the case where you're not using a package manager, you generally want to start evaluating at least some things.

So consider your example, but instead of this being a function that will draw a square on demand, you actually want to draw a square now. So you have something like what I proposed with the start method:

drawSquare = (x, y, size) ->
  drawLine x, y, x + size, y
  drawLine x + size, y, x + size, y + size

drawLine = (x1, y1, x2, y2) ->
  #...
drawSquare 10, 10, 100

Which as we're saying, is not ideal as it hides the central thing that your file is doing at the bottom of the file. This gets a lot worse if you want to do something more complex - like fire an ajax request to fetch the number of users and then draw a square for each of them, and maybe place info in each square. You end up with either all this code at the bottom, or in a start method at the very top. Quite icky.

My assertion is that this is not a fringe minority case, few enough people use module loaders that this is incredibly common, and javascript, for all its faults, used hoisting as a reasonable solution to the issue. Hoisting was abused to high-heaven, sure, but when used intentionally (as I'm suggesting coffeescript allow with a new assignment operator) it makes for much nicer code.

As for it being misleading when changing variables to functions, that is a fair point. It's not a huge issue to my style of coding but I could see that bothering some people more. I still think that my proposal comes with enough warning-bells. Suppose you went with my syntax proposal of a new symbol. Let's say something like this (I'm going to change it to caret cause I like ^= better than :=):

gravity ^= -> 9.8

In my proposal if you simply removed the function in favor of a scalar, this would not compile therefore giving you an extra indicator of something being fundamentally wrong.

You do make a good point about function decorators, this operator would have to take them into account and implement

veryExpensiveComputation ^= profile -> ...

as

function veryExpensiveComputation() {
  return profile.apply(this, function(){
    ...
  });
}

Not great, but not terribly difficult to implement either. My biggest preference is that the coffeescript compiler itself would do the hoisting, which would allow us to keep using function expressions and get rid of that mess above, but I have not convinced myself yet how good an idea that would be.

@jiangmiao

This comment has been minimized.

Show comment
Hide comment
@jiangmiao

jiangmiao Apr 23, 2013

Contributor

@togakangaroo I've implemented the named function in ToffeeScript 1.6.2-3. you could have a try.
The code which could compile to named function rule is if the function defined in the first level of code block and the function name haven't been used

a = -> # compile to named function
f = null
if true
  b = c ->  # isn't in first level of code block
d e = -> #  isn't in first level of code block
f = -> # the variable has been assigned
a = null

is compiled to

var b, e, f;

function a() {};

f = null;

if (true) {
  b = c(function() {});
}

d(e = function() {});

f = function() {};

a = null;

ToffeeScript base on CoffeeScript 1.6.2 and is fully compatible with CoffeeScript except for one case

# m never declared above, m must be local variable and assign to undefined
# and use constant undefined value m
m()
m = ->
m

in ToffeeScript m defined as function and hoisted, and in CoffeeScript m is undefined.

The project is at https://github.com/jiangmiao/toffee-script
or install by npm install toffee-script

Contributor

jiangmiao commented Apr 23, 2013

@togakangaroo I've implemented the named function in ToffeeScript 1.6.2-3. you could have a try.
The code which could compile to named function rule is if the function defined in the first level of code block and the function name haven't been used

a = -> # compile to named function
f = null
if true
  b = c ->  # isn't in first level of code block
d e = -> #  isn't in first level of code block
f = -> # the variable has been assigned
a = null

is compiled to

var b, e, f;

function a() {};

f = null;

if (true) {
  b = c(function() {});
}

d(e = function() {});

f = function() {};

a = null;

ToffeeScript base on CoffeeScript 1.6.2 and is fully compatible with CoffeeScript except for one case

# m never declared above, m must be local variable and assign to undefined
# and use constant undefined value m
m()
m = ->
m

in ToffeeScript m defined as function and hoisted, and in CoffeeScript m is undefined.

The project is at https://github.com/jiangmiao/toffee-script
or install by npm install toffee-script

@betlab

This comment has been minimized.

Show comment
Hide comment
@betlab

betlab Dec 2, 2014

+1
Use case:

doStuff() # I cause an error
# ... later
doStuff = (x,y) ->
  alert("Were I actually a named function, this would work!")

The equivalent JavaScript would work fine, with a real named function:

doStuff(); // I work just fine!
// later....
function doStuff() {
  alert("I'm a real named function!")
}

betlab commented Dec 2, 2014

+1
Use case:

doStuff() # I cause an error
# ... later
doStuff = (x,y) ->
  alert("Were I actually a named function, this would work!")

The equivalent JavaScript would work fine, with a real named function:

doStuff(); // I work just fine!
// later....
function doStuff() {
  alert("I'm a real named function!")
}
@aleclarson

This comment has been minimized.

Show comment
Hide comment
@aleclarson

aleclarson Feb 13, 2015

I really like @jiangmiao's solution.

My only need is being able to use MyConstructor.name when MyConstructor is declared as a function rather than with the class keyword. But this is a rare case.

This is my solution:

MyConstructor = `function MyConstructor () { 
  return MyConstructor.init.apply(null, arguments) 
}`
MyConstructor.init = (someValues...) ->
  # Do your thing
alert MyConstructor.name

Here's a JSFiddle testing the above code.

P.S. I'm a new user of Coffeescript and I fucking love it. Thanks for everything. 😄

aleclarson commented Feb 13, 2015

I really like @jiangmiao's solution.

My only need is being able to use MyConstructor.name when MyConstructor is declared as a function rather than with the class keyword. But this is a rare case.

This is my solution:

MyConstructor = `function MyConstructor () { 
  return MyConstructor.init.apply(null, arguments) 
}`
MyConstructor.init = (someValues...) ->
  # Do your thing
alert MyConstructor.name

Here's a JSFiddle testing the above code.

P.S. I'm a new user of Coffeescript and I fucking love it. Thanks for everything. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment