4. Functions and Arguments

In this chapter we are going to look at one of the most essential parts of any language, the function. Functions allow us to encapsulate reusable and discrete code blocks. Without functions our code would be one long, unreadable, and unmaintainable mess.

I wanted to give you an example of what JavaScript would look like if we were not able to use or write functions, but I was unable to. Even the simplest example of taking a string and making it lowercase requires the use of functions in JavaScript.

Because I can’t show you an example devoid of functions, I’ll show you an example of some CoffeeScript code that could use the help of a function or two, so you can see how important functions are to helping you keep your code manageable.

Example: (source: no_functions_example.coffee)


tax_rate = 0.0625

val = 100
console.log "What is the total of $#{val} worth of shopping?"
tax = val * tax_rate
total = val + tax
console.log "The total is #{total}"

val = 200
console.log "What is the total of $#{val} worth of shopping?"
tax = val * tax_rate
total = val + tax
console.log "The total is #{total}"


Example: (source: no_functions_example.js)


(function() {
  var tax, tax_rate, total, val;

  tax_rate = 0.0625;

  val = 100;

  console.log("What is the total of $" + val + " worth of shopping?");

  tax = val * tax_rate;

  total = val + tax;

  console.log("The total is " + total);

  val = 200;

  console.log("What is the total of $" + val + " worth of shopping?");

  tax = val * tax_rate;

  total = val + tax;

  console.log("The total is " + total);

}).call(this);


Output: (source: no_functions_example.coffee)


What is the total of $100 worth of shopping?
The total is 106.25
What is the total of $200 worth of shopping?
The total is 212.5


In our example, we are calculating the total value of goods purchased in-state with certain sales tax. Apart from the banality of the example, you can see that we are repeating our code to calculate the total value with tax several times.

Let’s refactor our code a bit, add some functions, and try to clean it up.

Example: (source: with_functions_example.coffee)


default_tax_rate = 0.0625

calculateTotal = (sub_total, rate = default_tax_rate) ->
  tax = sub_total * rate
  sub_total + tax

val = 100
console.log "What is the total of $#{val} worth of shopping?"
console.log "The total is #{calculateTotal(val)}"

val = 200
console.log "What is the total of $#{val} worth of shopping?"
console.log "The total is #{calculateTotal(val)}"


Example: (source: with_functions_example.js)


(function() {
  var calculateTotal, default_tax_rate, val;

  default_tax_rate = 0.0625;

  calculateTotal = function(sub_total, rate) {
    var tax;
    if (rate == null) rate = default_tax_rate;
    tax = sub_total * rate;
    return sub_total + tax;
  };

  val = 100;

  console.log("What is the total of $" + val + " worth of shopping?");

  console.log("The total is " + (calculateTotal(val)));

  val = 200;

  console.log("What is the total of $" + val + " worth of shopping?");

  console.log("The total is " + (calculateTotal(val)));

}).call(this);


Output: (source: with_functions_example.coffee)


What is the total of $100 worth of shopping?
The total is 106.25
What is the total of $200 worth of shopping?
The total is 212.5


You probably don’t understand everything we just did there, but don’t worry, that’s what this chapter is for. However, even without knowing the specifics of how functions are defined, and work, in CoffeeScript, you can see how much cleaner our code is between the two examples. In the refactored code, we are even able to pass in a different tax rate, should we need to. This also helps us keep our code DRY1: Don’t Repeat Yourself. Not repeating your code makes for an easier-to-manage code base with, hopefully, fewer bugs.

Function Basics

We’ll start with the very basics on how to define a function in CoffeeScript. The anatomy of a very simple function looks like this:

Example: (source: simple_function.coffee)


myFunction = ()->
  console.log "do some work here"

myFunction()


Example: (source: simple_function.js)


(function() {
  var myFunction;

  myFunction = function() {
    return console.log("do some work here");
  };

  myFunction();

}).call(this);


In that example we gave the function a name, myFunction, and a code block to go with it. The body of the function is the code that is indented below the ->, following the significant whitespace rules we learned about in Chapter 2, “The Basics.”

The function does not accept any arguments. We know that by the empty parentheses prior to the ->. When calling a function in CoffeeScript that has no arguments, we are required to use parentheses, myFunction().

Because our function has no arguments, we can drop the parentheses entirely when defining it, like so:

Example: (source: simple_function_no_parens.coffee)


myFunction = ->
  console.log "do some work here"

myFunction()


Example: (source: simple_function_no_parens.js)


(function() {
  var myFunction;

  myFunction = function() {
    return console.log("do some work here");
  };

  myFunction();

}).call(this);


There is one more way we can write this simple function. Because the body of our function is on only one line, we can collapse the whole function definition to one, like this:

Example: (source: simple_function_one_line.coffee)


myFunction = -> console.log "do some work here"

myFunction()


Example: (source: simple_function_one_line.js)


(function() {
  var myFunction;

  myFunction = function() {
    return console.log("do some work here");
  };

  myFunction();

}).call(this);


All three of the previous code examples produce the same JavaScript and are called in the same way.


Tip

Although you can write function definitions on one line, I prefer not to. Personally, I don’t find it that much cleaner or easier to read. Also, by keeping the body of the function on a separate line, you make it easier to later augment your function with more code.

You should also notice that the last line of each function contains a return keyword. CoffeeScript adds this automatically for you. Whatever the last line of your function is, that will be the function’s return value. This is similar to languages such as Ruby. Because CoffeeScript will automatically add the return for you in the compiled JavaScript, the use of the return keyword in your CoffeeScript is optional.



Tip

I find that adding the return keyword can sometimes help make the meaning of your code a bit clearer. Use it where you find it will help make your code easier to read and understand.



Tip

If you want your functions to not return the last line of the function, you’ll have to explicitly give it a new last line to return. Something like return null or return undefined will do the trick nicely.


Arguments

Just like in JavaScript, functions in CoffeeScript can also take arguments. Arguments let us pass objects into the function so that the function can then perform calculations, data manipulation, or whatever our little hearts desire.

In CoffeeScript, defining a function that takes arguments is not much different than in JavaScript. Inside our parentheses we define a comma-separated list of the names of the arguments we want the function to accept.

Example: (source: function_with_args.coffee)


calculateTotal = (sub_total, rate) ->
  tax = sub_total * rate
  sub_total + tax

console.log calculateTotal(100, 0.0625)


Example: (source: function_with_args.js)


(function() {
  var calculateTotal;

  calculateTotal = function(sub_total, rate) {
    var tax;
    tax = sub_total * rate;
    return sub_total + tax;
  };

  console.log(calculateTotal(100, 0.0625));

}).call(this);


Output: (source: function_with_args.coffee)


106.25


As you can see in our example, we defined our function to take in two arguments and to do some math with them to calculate a total value. When we called the function, we passed in the two values we wanted it to use.

In Chapter 2 we discussed briefly the rules around parentheses in CoffeeScript. I want to reiterate one of those rules. Because our function takes arguments, we are allowed to omit the parentheses when calling the function. This means we could also write our example like this:

Example: (source: function_with_args_no_parens.coffee)


calculateTotal = (sub_total, rate) ->
  tax = sub_total * rate
  sub_total + tax

console.log calculateTotal 100, 0.0625


Example: (source: function_with_args_no_parens.js)


(function() {
  var calculateTotal;

  calculateTotal = function(sub_total, rate) {
    var tax;
    tax = sub_total * rate;
    return sub_total + tax;
  };
  console.log(calculateTotal(100, 0.0625));

}).call(this);


Output: (source: function_with_args_no_parens.coffee)


106.25


As you can see, CoffeeScript correctly compiled the JavaScript for us, putting those parentheses back where they are needed.


Tip

The use of parentheses when calling functions is hotly contested in the CoffeeScript world. Personally, I tend to use them. I think it helps make my code a bit more readable, and it cuts down on potential bugs where parentheses were misplaced by the compiler. When in doubt, use parentheses. You won’t regret it.


Default Arguments

In some languages, such as Ruby, it is possible to assign default values to arguments. This means that if you do not pass in some arguments, for whatever reason, then reasonable default values can be used in their place.

Let’s revisit our calculator example again. We’ll write it so that the tax rate is set to a default value should one not be passed in:

Example: (source: default_args.coffee)


calculateTotal = (sub_total, rate = 0.05) ->
  tax = sub_total * rate
  sub_total + tax

console.log calculateTotal 100, 0.0625
console.log calculateTotal 100


Example: (source: default_args.js)


(function() {
  var calculateTotal;

  calculateTotal = function(sub_total, rate) {
    var tax;
    if (rate == null) rate = 0.05;
    tax = sub_total * rate;
    return sub_total + tax;
  };

  console.log(calculateTotal(100, 0.0625));

  console.log(calculateTotal(100));

}).call(this);


Output: (source: default_args.coffee)


106.25
105


When defining our function, we told CoffeeScript to set the default value of the tax_rate argument equal to 0.05. When we first call the calculateTotal function, we pass in a tax_rate argument of 0.0625; the second time we omit the tax_rate argument altogether, and the code does the appropriate thing and uses 0.05 in its place.

We can take default arguments a step further and have them refer to other arguments. Consider this example:

Example: (source: default_args_referring.coffee)


href = (text, url = text) ->
  html = "<a href='#{url}'>#{text}</a>"
  return html

console.log href("Click Here", "http://www.example.com")
console.log href("http://www.example.com")


Example: (source: default_args_referring.js)


(function() {
  var href;

  href = function(text, url) {
    var html;
    if (url == null) url = text;
    html = "<a href='" + url + "'>" + text + "</a>";
    return html;
  };
  console.log(href("Click Here", "http://www.example.com"));

  console.log(href("http://www.example.com"));

}).call(this);


Output: (source: default_args_referring.coffee)


<a href='http://www.example.com'>Click Here</a>
<a href='http://www.example.com'>http://www.example.com</a>


Should no one pass in the url argument in our example, we will set it equal to the text argument that was passed in.

It is also possible to use functions as default values in the argument list. Because the default value will be called only if there is no argument passed in, there is no performance concern.

Example: (source: default_args_with_function.coffee)


defaultRate = -> 0.05

calculateTotal = (sub_total, rate = defaultRate()) ->
  tax = sub_total * rate
  sub_total + tax

console.log calculateTotal 100, 0.0625
console.log calculateTotal 100


Example: (source: default_args_with_function.js)


(function() {
  var calculateTotal, defaultRate;

  defaultRate = function() {
    return 0.05;
  };

  calculateTotal = function(sub_total, rate) {
    var tax;
    if (rate == null) rate = defaultRate();
    tax = sub_total * rate;
    return sub_total + tax;
  };
  console.log(calculateTotal(100, 0.0625));

  console.log(calculateTotal(100));

}).call(this);


Output: (source: default_args_with_function.coffee)


106.25
105



Tip

When using default arguments it is important to note that they must be at the end of the argument list. It is okay to have multiple arguments with defaults, but they all must be at the end.


Splats...

Sometimes when developing a function, we are not sure just how many arguments we are going to need. Sometimes we might get one argument; other times we might get a hundred. To help us easily solve this problem, CoffeeScript gives us the option of using splats when defining the argument list for a function. Splatted arguments are denoted by placing an ellipsis (...) after the method definition.


Tip

A great way to remember how to use splats is to treat the ... suffix as if you were saying etc... Not only is that easy to remember, but if you use etc... in your code, you’ll look cool.


When would you use splats? Splats can be used whenever your function will be taking in a variable number of arguments. Before we take a look at a detailed example, let’s look quickly at a simple function that takes a splatted argument:

Example: (source: splats.coffee)


splatter = (etc...) ->
  console.log "Length: #{etc.length}, Values: #{etc.join(', ')}"

splatter()
splatter("a", "b", "c")


Example: (source: splats.js)


(function() {
  var splatter,
    __slice = Array.prototype.slice;

  splatter = function() {
    var etc;
    etc = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
    return console.log("Length: " + etc.length + ", Values: " + (etc.join(', ')));
  };

  splatter();

  splatter("a", "b", "c");

}).call(this);


Output: (source: splats.coffee)


Length: 0, Values:
Length: 3, Values: a, b, c


As you can see, whatever arguments we pass into our function automatically get put into an array, and should we not send any arguments we get an empty array.


Tip

Splats are a great example of something that can be done in JavaScript but would require a lot of boilerplate code to implement. Look at the JavaScript output of a CoffeeScript splatted argument and you’ll agree boilerplate code is no fun to write.


Unlike other languages that support a similar construct, CoffeeScript does not force you to only use splats as the last argument in the argument list. In fact, splatted arguments can appear anywhere in your argument list. A small caveat is that you can have only one splatted argument in the argument list.

To help illustrate how splats can be used in any part of the argument list, let’s write a method that will take some arguments and spit out a string. When building this string, we make sure that the first and last arguments are uppercased; any other arguments will be lowercased. Then we’ll concatenate the string using forward slashes.

Example: (source: splats_arg_join.coffee)


joinArgs = (first, middles..., last) ->
  parts = []

  if first?
    parts.push first.toUpperCase()

  for middle in middles
    parts.push middle.toLowerCase()

  if last?
    parts.push last.toUpperCase()

  parts.join('/')

console.log joinArgs("a")
console.log joinArgs("a", "b")
console.log joinArgs("a", "B", "C", "d")


Example: (source: splats_arg_join.js)


(function() {
  var joinArgs,
    __slice = Array.prototype.slice;

  joinArgs = function() {
    var first, last, middle, middles, parts, _i, _j, _len;
    first = arguments[0], middles = 3 <= arguments.length ? __slice.call(arguments, 1, _i = arguments.length - 1) : (_i = 1, []), last = arguments[_i++];
    parts = [];
    if (first != null) parts.push(first.toUpperCase());
    for (_j = 0, _len = middles.length; _j < _len; _j++) {
      middle = middles[_j];
      parts.push(middle.toLowerCase());
    }
    if (last != null) parts.push(last.toUpperCase());
    return parts.join('/'),
  };

  console.log(joinArgs("a"));

  console.log(joinArgs("a", "b"));

  console.log(joinArgs("a", "B", "C", "d"));

}).call(this);


Output: (source: splats_arg_join.coffee)


A
A/B
A/b/c/D


I admit that is a bit of a heavy example, but it illustrates how splats work. When we call the joinArgs function, the first argument we pass into the function call gets assigned to the first variable, the last argument we pass in gets assigned to the last variable, and if any other arguments are passed in between the first and the last arguments, those are put into an array and assigned to the middles variable.


Tip

We could have written our function to just take a splatted argument and extract the first and last elements from the middles array, but this function definition means we don’t have to write all that code. Happy days.


Finally, when dealing with splats, you might have an array that you want passed in as individual arguments. That is possible.

Let’s take a quick look at an example:

Example: (source: splats_array.coffee)


splatter = (etc...) ->
  console.log "Length: #{etc.length}, Values: #{etc.join(', ')}"

a = ["a", "b", "c"]
splatter(a)
splatter(a...)


Example: (source: splats_array.js)


(function() {
  var a, splatter,
    __slice = Array.prototype.slice;

  splatter = function() {
    var etc;
    etc = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
    return console.log("Length: " + etc.length + ", Values: " + (etc.join(', ')));
  };

  a = ["a", "b", "c"];
  splatter(a);

  splatter.apply(null, a);

}).call(this);


Output: (source: splats_array.coffee)


Length: 1, Values: a,b,c
Length: 3, Values: a, b, c


Using our earlier splatter example, we can try first passing in an array, but as you can see, the splatter function sees the array as a single argument, because that is what it is. However, if we append ... to the array as we pass it into our function call, the CoffeeScript will split up the array into separate arguments and pass them into the function.

Wrapping Up

There you have it—everything you’ve ever wanted to know about functions in CoffeeScript! First we looked at how to define a simple function; in fact, we saw several ways to define a function in CoffeeScript. We then took a look at how arguments to functions are defined and how to call a function, including a recap of when and where you do and do not have to use parentheses when calling a function. We also took a look at default arguments, one of my favorite features of CoffeeScript.

Finally, we explored splats and how they help us write functions that take variable arguments.

With our nickel tour of functions and arguments over with, we can move on to the next stop, Chapter 5, “Collections and Iterations.” So go grab a cold one, and we’ll meet there. Ready?

Notes

1. http://en.wikipedia.org/wiki/DRY

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.135.247.68