Function declaration in CoffeeScript
Asked Answered
A

7

80

I notice that in CoffeeScript, if I define a function using:

a = (c) -> c=1

I can only get the function expression:

var a;
a = function(c) {
    return c = 1;
};

But, personally I often use function declaration,for example:

function a(c) {
    return c = 1;
}

I do use the first form, but I'm wondering if there is a way in CoffeeScript generating a function declaration. If there is no such way, I would like to know why CoffeeScript avoid doing this. I don't think JSLint would holler an error for declaration, as long as the function is declared at the top of the scope.

Arsenal answered 1/7, 2011 at 13:42 Comment(9)
Do you have any good reason to want function declaration? If your using coffeescript you should not care about the format of the compiled JS unless it's broken / bugged.Subgroup
In most cases, function declaration and function expression work the same way, but there is a slightly difference between the two. For example, developer.mozilla.org/en/JavaScript/Reference/… So, in some cases, they are not equal.Arsenal
you linked me to a piece of code where the function declaration is undefined behaviour. Do you want to use function declarations instead of function expressions so that you can abuse undefined behaviour?Subgroup
@Subgroup Function declarations can be nice for stack traces and other debugging, since a name is attached to the function. That's why CoffeeScript uses them for classes.Ripe
@TrevorBurnham that's a different issue. That's one of the reasons I dont use CS, debugging compiled js is a pain.Subgroup
@Subgroup How's it a different issue? You wanted a good reason for function declaration. function a(c) giving you clearer stack traces than a = function(c) is a pretty good reason.Ripe
@TrevorBurnham I meant that's only a minor improvement on the difficulty of debugging compiled js. What you actually want is a debugger that can read coffeescript.Subgroup
@TrevorBurnham, I did not notice differences in their stack traces, but it is great to know.Arsenal
@TrevorBurnham Even if the debugger could read coffeescript, that wouldn't help. The Javascript which CS is translated to doesn't have named functions, therefore the debugger cannot display them. Analyzing the source code after the fact to figure out which variable the function may or may not be assigned to seems too fragile to me.Calvin
R
61

CoffeeScript uses function declarations (aka "named functions") in just one place: class definitions. For instance,

class Foo

compiles to

var Foo;
Foo = (function() {
  function Foo() {}
  return Foo;
})();

The reason CoffeeScript doesn't use function declarations elsewhere, according to the FAQ:

Blame Microsoft for this one. Originally every function that could have a sensible name retrieved for it was given one, but IE versions 8 and down have scoping issues where the named function is treated as both a declaration and an expression. See this for more information.

In short: Using function declarations carelessly can lead to inconsistencies between IE (pre-9) and other JS environments, so CoffeeScript eschews them.

Ripe answered 1/7, 2011 at 15:32 Comment(2)
He's talking about IE's issue with Named Function Expressions (e.g. var a = function a() {};). Function Declarations (e.g. function a() {}) have no such cross browser inconsistenciesChromo
This would make more sense to me if it wasn't insane to use CS in a browser in the first place. It's one thing to put your faith in a DOM-handling library to keep up with browser variations and deprecation but when the thing you're talking about is the actual source code itself, that's like a double-jeopardy dependency. Imagine dealing with a legacy code base 10 years after the CS community dried up and moved on to the next make-it-more-rails-like-for-me phenomenon. When everything starts breaking and it's on you to find out what got post-deprecated and figure out what to fix in the CS parser.Segregationist
P
12

Yes you can:

hello()

`function hello() {`
console.log 'hello'
dothings()
`}`

You escape pure JS via the backtick `

Note that you can't indent on your function body.

Cheers

Polak answered 25/3, 2014 at 0:38 Comment(4)
This does not show it being done in coffeescript - only that coffeescript allows escaping to javascript. Also this is naaaasty!Meggs
Definitions before usage is more nasty xDPolak
Also, function declarations appear to be heavily optimized in later versions of v8.Claxton
note that you can write function updateSettings() {; do -> dothings() } to allow for indentation. github.com/jashkenas/coffeescript/issues/…Mccollum
P
6

One thing to keep in mind with CoffeeScript is that you can always kick back to JavaScript. While CoffeeScript doesn't support named function declarations, you can always drop back to JavaScript to do it.

http://jsbin.com/iSUFazA/11/edit

# http://jsbin.com/iSUFazA/11/edit
# You cannot call a variable function prior to declaring it!
# alert csAddNumbers(2,3) # bad!

# CoffeeScript function
csAddNumbers = (x,y) -> x+y

# You can call a named function prior to
# delcaring it
alert "Calling jsMultiplyNumbers: " + jsMultiplyNumbers(2,3) # ok!

# JavaScript named function
# Backticks FTW!
`function jsMultiplyNumbers(x,y) { return x * y; }`

You can also write a big fat function in CoffeeScript and then just use the backticks trick to have JavaScript call the other function:

# Coffeescript big function
csSomeBigFunction = (x,y) ->
   z = x + y
   z = z * x * y
   # do other stuff
   # keep doing other stuff

# Javascript named function wrapper
`function jsSomeBigFunction(x,y) { return csSomeBigFunction(x,y); }`
Planking answered 27/10, 2013 at 18:47 Comment(0)
C
1

No, you can't define a function in coffee script and have it generate a function declaration in coffee script

Even if you just write

-> 123

the generated JS will be wrapped in parens, thus making it a function expression

(function() {
  return 123;
});

My guess is that this is because function declarations get "hoisted" to the top of the enclosing scope which would break up the logical flow of the coffeescript source.

Chromo answered 13/10, 2011 at 18:18 Comment(3)
The hoisting is exactly why I want to use function declarations!Photophobia
CoffeeScript in a sense already "hoists" because it predeclares variables with var at the top of the scope. So functions can refer to each other and order doesn't matter.Unkindly
@EvanMoran It's true that CoffeeScript pre-declares the variables, but the functions are not hoisted because the variable remains undefined until the function expression. Hence, you cannot use the functions until after they are defined.Preestablish
R
1

While this is an older post, I wanted to add something to the conversation for future Googlers.

OP is correct in that we cannot declare functions in pure CoffeeScript (excluding the idea of using back-ticks to escape pure JS inside the CoffeeScript file).

But what we can do is bind the function to the window and essentially end up with something we can call as though it was a named function. I am not stating this is a named function, I'm providing a way to do what I imagine OP wants to actually do (call a function like foo(param) somewhere in the code) using pure CoffeeScript.

Here is an example of a function attached to the window in coffeescript:

window.autocomplete_form = (e) ->
    autocomplete = undefined
    street_address_1 = $('#property_street_address_1')
    autocomplete = new google.maps.places.Autocomplete(street_address_1[0], {})
    google.maps.event.addListener autocomplete, "place_changed", ->
        place = autocomplete.getPlace()

        i = 0

        while i < place.address_components.length
            addr = place.address_components[i]
            st_num = addr.long_name if addr.types[0] is "street_number"
            st_name = addr.long_name if addr.types[0] is "route"

            $("#property_city").val addr.long_name if addr.types[0] is "locality"
            $("#property_state").val addr.short_name if addr.types[0] is "administrative_area_level_1"
            $("#property_county").val (addr.long_name).replace(new RegExp("\\bcounty\\b", "gi"), "").trim() if addr.types[0] is "administrative_area_level_2"
            $("#property_zip_code").val addr.long_name if addr.types[0] is "postal_code"
            i++

        if st_num isnt "" and (st_num?) and st_num isnt "undefined"
            street1 = st_num + " " + st_name
        else
            street1 = st_name

        street_address_1.blur()
        setTimeout (->
            street_address_1.val("").val street1
            return
            ), 10
        street_address_1.val street1
        return

This is using Google Places to return address information to auto-populate a form.

So we have a partial in a Rails app which is being loaded into a page. This means the DOM is already created, and if we call the function above on initial page load (before the ajax call renders the partial), jQuery won't see the $('#property_street_address_1') element (trust me - it didn't).

So we need to delay the google.maps.places.Autocomplete() until after the element is present on the page.

We can do this via the Ajax callback on successful load of the partial:

            url = "/proposal/"+property_id+"/getSectionProperty"
            $("#targ-"+target).load url, (response, status, xhr) ->
                if status is 'success'
                    console.log('Loading the autocomplete form...')
                    window.autocomplete_form()
                    return

            window.isSectionDirty = false

So here, essentially, we're doing the same thing as calling foo()

Radiotransparent answered 9/10, 2014 at 16:34 Comment(0)
L
1

Why? Because function declaration is evil. Look at this code

function a() {
        return 'a';
}

console.log(a());

function a() {
        return 'b';
}

console.log(a());

What will be on the output?

b
b

If we use the function definition

var a = function() {
        return 'a';
}

console.log(a());

a = function() {
        return 'b';
}

console.log(a());

the output is:

a
b
Lysippus answered 2/11, 2015 at 15:4 Comment(2)
There's nothing evil about function declarations. You just need to understand how variable and function declarations are hoisted in JS. Further reading on variable hoisting and function hoistingCounterpoison
function definition is more intitive than function declaration.Lysippus
C
0

Try this:

defineFct = (name, fct)->
  eval("var x = function #{name}() { return fct.call(this, arguments); }")
  return x

Now the following will print "true":

foo = defineFct('foo', ()->'foo')
console.log(foo() == foo.name)

I don't actually use this, but do sometimes wish coffee functions had names for introspection.

Colza answered 21/10, 2014 at 1:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.