Exploring CoffeeScript Part 3 – More on Functions
For this blog post we?re going to continue where we previously left off, talking a bit more about functions in CoffeeScript. Let?s get things started by talking a bit about function scope.
Function scope
Like Ruby, CoffeeScript uses lexical scope. This means that a variable that is declared inside a function is invisible to outside code. From there on, a variable lives inside the scope in which a value is assigned to it without being able to be redeclared. Because we don’t have access to the var keyword, we can’t simply declare a variable and use it in a different scope without assigning a value to it.
Consider the following code:
someFunction = -> podcast = 'Hardcore history' podcast = 'Astronomy cast' someFunction() console.log podcast # This outputs 'Astronomy cast'
The output might come as a surprise to you, but it actually makes a lot of sense. A variable that is declared inside an inner scope cannot shadow a variable with the same name that is declared in an outer scope. I highly recommend that you avoid reusing variable names as shown by this example.
Function context
In JavaScript, the this keyword inside a function does not necessarily point to the object for which the function is declared. The object that is referenced by this depends on how the function is called. The same applies to CoffeeScript as well, only it uses the ‘@’ symbol for denoting the this keyword in JavaScript. Let?s look at a couple of scenarios.
download = (podcast) -> @podcast = podcast console.log @podcast # This outputs 'Astronomy Cast' console.log @ # This outputs all properties of the global object download 'Astronomy Cast'
Compiling this down to JavaScript results in the following code:
var download; download = function(podcast) { this.podcast = podcast; console.log(this.podcast); return console.log(this); }; download('Astronomy Cast');
As you can see, @podcast is the shorthand notation for this.podcast in JavaScript. For this example, this references the global object. Let?s have a look at another scenario.
Podcast = () -> @name = 'Astronomy Cast' @download = (episode) -> console.log 'Downloading ' + episode + ' of ' + @name return @ # Outputs 'Downloading the first episode of Astronomy Cast' podcast = new Podcast podcast.download 'the first episode'
This results in the following JavaScript code:
var Podcast, podcast; Podcast = function() { this.name = 'Astronomy Cast'; this.download = function(episode) { return console.log('Downloading ' + episode + ' of ' + this.name); }; return this; }; podcast = new Podcast; podcast.download('the first episode');
When calling a constructor function using the new operator, this references the new object being created. We’ve also explicitly added ‘return @’ in order to return the new object. Otherwise the function assigned to the download property is implicitly returned. Let?s move on to the next case.
podcast = name: 'Astronomy Cast' download: (episode) -> console.log 'Downloading ' + episode + ' of ' + @name otherPodcast = name: 'Hardcore History' # Outputs 'Downloading the first episode of Hardcore History' podcast.download.apply otherPodcast, ['the first episode']
Compiling this to JavaScript results into the following code:
var otherPodcast, podcast; podcast = { name: 'Astronomy Cast', download: function(episode) { return console.log('Downloading ' + episode + ' of ' + this.name); } }; otherPodcast = { name: 'Hardcore History' }; podcast.download.apply(otherPodcast, ['the first episode']);
As I already mentioned in a previous blog post, we can reuse methods from other objects using the call()/apply() methods defined on the prototype of Function. In this example we reuse the download function in the context of the otherPodcast object. This means that this now references otherPodcast instead of the podcast object. Let?s dive into the final and most common scenario.
podcast = name: 'Astronomy Cast' download: (episode) -> console.log 'Downloading ' + episode + ' of ' + @name # Outputs 'Downloading the first episode of Astronomy Cast' podcast.download('the first episode')
The equivalent JavaScript code looks like this:
var podcast; podcast = { name: 'Astronomy Cast', download: function(episode) { return console.log('Downloading ' + episode + ' of ' + this.name); } }; podcast.download('the first episode');
Here this simply references the current object to which the executing function belongs to.
Function binding
Going over these different scenarios regarding the function context, it might be useful to be able to define a function that binds to the current value of this. In CoffeeScript we can use the => symbol instead of ?> to accomplish just that. Let?s look at the code example from the CoffeeScript website.
Account = (customer, cart) -> @customer = customer @cart = cart $('.shopping_cart').bind 'click', (event) => @customer.purchase @cart
This is especially helpful for event callbacks. The reason you might want to consider the ?fat arrow? in this case is that callbacks are executed in the context of the element, i.e. this references the element. When you want to keep this equal to the local context, without doing the self = this dance, then ?fat arrows? are the way to go.
Splats
Splats enable us to easily take a variable number of parameters in a function. JavaScript has a similar construct with the arguments object that is available in every function. But the major downside of the arguments object is that it lacks the semantics of an array which makes it pretty cumbersome to use. With splats, CoffeeScript provides us with a much better way to deal with a varying number of parameters.
download = (name, episodes..., epilogue) -> console.log 'Downloading ' + episodes.join(', ') + ' of ' + name; console.log epilogue # Outputs 'Downloading the first, the second, and third episode of Astronomy Cast' # 'What a hoot!' download('Astronomy Cast', 'the first', 'the second', 'and third episode', 'What a hoot!')
This boils down to the following (nasty) JavaScript code:
var download; var __slice = Array.prototype.slice; download = function() { var epilogue, episodes, name, _i; name = arguments[0], episodes = 3 <= arguments.length ? __slice.call(arguments, 1, _i = arguments.length - 1) : (_i = 1, []), epilogue = arguments[_i++]; console.log('Downloading ' + episodes.join(', ') + ' of ' + name); return console.log(epilogue); }; download('Astronomy Cast', 'the first', 'the second', 'and third episode', 'What a hoot!');
As you can see, splats don’t have to be defined at the end of the parameter list. For this example, the first and last value get to be assigned to the regular parameters after which the remaining values in the middle of the list get assigned to the splat parameter. When we only specify two values, then these will be used for the regular parameters while the splat parameter will be null.
The values for the regular parameters get to be assigned first after which the remaining values all get to be passed to the splat parameter. This also implies that there can be only a single splat parameter for a given function definition.
Splats are not only available for function definitions, but also for calling functions as well.
download = (name, episode, epilogue) -> console.log 'Downloading ' + episode + ' of ' + name; console.log epilogue # Outputs 'Downloading the first episode of Astronomy Cast' # 'What a hoot!' arguments = ['Astronomy Cast', 'the first episode', 'What a hoot!'] download(arguments...)
This simply boils down to calling the apply() method of the download function in the resulting JavaScript code:
var download; download = function(name, episode, epilogue) { console.log('Downloading ' + episode + ' of ' + name); return console.log(epilogue); }; arguments = ['Astronomy Cast', 'the first episode', 'What a hoot!']; download.apply(null, arguments);
No doubt that CoffeeScript brings a lot of goodness to the table when it comes to functions. But there?s also a lot more. Stay tuned for the next couple of blog posts.
Until next time.
Can you use the shorter syntax of the @ symbol to save you from having to make the assignment call at the first line of the function, or is that syntax only valid for class constructors? Example:
download = (@podcast) ->
# method body here but without @podcast = podcast line
Yes, that works both for classes and functions. Under the hood, they are both functions.