CoffeeScript: Continuing Down the Rabbit Hole

Spread the word
Tweet about this on TwitterShare on Google+Share on FacebookPin on PinterestShare on RedditShare on TumblrEmail this to someonePrint this page

Functions

Functions are written differently in CoffeeScript than plain JavaScript. Firstly, in CS you cannot do function hello_world() { … } but rather all functions are set to a variable. Next Instead of writing the keyword function we simply use the parentheses to contain our parameters followed by a skinny arrow. (hyphen and greater than symbol) The contents of the function go to the next line and are tabbed in once.

CoffeeScript:

  hello_world = () ->
     console.log “Hello World"

Compiles to:

  var hello_world = function() {
     console.log(“Hello World”);
  }

Here we can also see that calling console.log(), we do not need the parentheses. This works for any time you are calling a function or method with one or more arguments. Let’s see how this works if we alter hello_world() like so:

CoffeeScript:

  hello_world = (to, from) ->
     if confirm “Send message to #{to}?”
       alert “Hello World and #{to}, from your friend: #{from}”

  goodbye_you = ->
     alert “You don’t have to go home, but you can’t stay here"

  $ ->
     hello_world “Fred”, “Bob”
     goodbye_you()

Here, because hello_world() now takes two arguments we no longer have to use the parentheses. Yet goodbye_you(), not having any arguments will require them. This is because we don’t want CoffeeScript to decide that goodbye_you is a new variable rather than an already declared function.

Why don’t we go further down the rabbit’s hole here? Look at the goodbye_you() function. What are we missing? Or rather not missing? (enter appropriate level of “lol” here) Because goodbye_you() doesn’t take any arguments we do not need to use the parentheses with the skinny arrow. This might seem intentionally confusing at first, but trust me, once you start get it, you’ll love it.

Sum up:

  • Declare a function
    • with parameters -> need parentheses
    • without parameters -> do not need parentheses
  • Call a function
    • with arguments -> do not need parentheses
    • without arguments -> need parentheses

Another quick note: $ -> is the jQuery $(document).ready(function() { … }); How awesome is that? Look at all those characters that we just don’t have to type anymore.

Variables

You still use var, ew…

In CoffeeScript you do not need to declare a variable with the keyword var

Javascript:

  var person = {
     first_name: “Bob”,
     last_name: “Sagat”,
     job: “Comedian”
  }
  var x = 35,
        y = 9;

  function who_is(person) {
    var first_name = person.first_name,
          last_name = person.last_name;
    console.log(first_name, last_name);
  }

CoffeeScript:

   person =
     first_name: “Bob”
     last_name: “Sagat”
     job: “Comedian”

  x = 35
  y = 9

  who_is = (person) ->
     first_name = person.last_name
     last_name = person.last_name
     console.log first_name, last_name

Compiles to:

  var person, x, y;
  
  person = {
     first_name: “Bob”,
     last_name: “Sagat”,
     job: “Comedian”
  }
  x = 35;
  y = 9;

  function who_is(person) {
    var first_name, last_name;

    first_name = person.first_name;
    last_name = person.last_name;
    console.log(first_name, last_name);
  }

Notice that the compiled JS declares the variable at the very top of its scope without setting a value. So person, x and y are scoped to the document and first_name and last_name are scoped only to the function who_is().

But wait, there’s more! Let’s revisit person.first_name and person.last_name.

  person =
     first_name: “Bob”
     last_name: “Sagat”
     job: “Comedian”

  who_is = (person) ->
     {first_name, last_name, job} = person
     console.log(first_name, last_name);

Say what?! That’s right, {first_name, last_name, job} = person will set the variables first_name, last_name and job to attributes set in person with the same names.

Arrays and Objects

Arrays

Arrays are rather standard in CoffeeScript, they are still initiated by square brackets [ ] and each item in the array is separated by a comma.

my_array = [‘Rome’, ‘Carthage’, ‘Syracuse’, ‘Athens’, ‘Alexandria']

Though it should be mentioned that the commas can be omitted from an array if it is written like so:

  my_array = [
       ‘Rome’
       ‘Carthage’
       ‘Syracuse’
       ‘Athens’
       ‘Alexandria’
  ]

I have only found this useful when using RequireJS though because it makes all the required attributes easier to read, sort and add to. In the code snippet below we see the define() method takes an argument that it is expecting to be an array. For readability’s sake I put each item on its own line and thus do not need the comma.

  define [
    'jquery'
    'underscore'
    'backbone'
    'html2canvas'
    'models/map'
    'models/user'
    'views/maps/map_view'
    'views/users/sidebar'
    'hbars!templates/maps/map'
  ], ($, _, Backbone, html2canvas, Map, User, MapView, Sidebar, newMap) ->
    … omitted code ...
Objects

Objects on the other hand benefit greatly from the use of CoffeeScript. I do a lot of BackboneJS which is all Object-Oriented and everything is an object or class. Within this structure these objects are mostly a collection of methods which gets pretty gross in plain JavaScript, but are rather nice and easy to read in CS. Written without the extra curly braces, without commas and forced formatting of CS these objects become quite pleasant to work in.

But let’s start with something a little easier:

  rome = “Empire in Italy"

  person =
     first_name: ‘Hannibal’
     last_name: ‘Barca’
     from: ‘Carthage'
     job: ‘General’
     mission: () ->
       if rome? 
          destroy_rome() 
       else 
          expand_carthage_influence()

  destroy_rome = () ->
     rome = “dust”

  expand_carthage_influence = () ->
     invade_spain()

Please excuse the sudden ancient history in the examples, I was playing Rome Total War 2 just before writing this section.

In this object we can see that I have omitted the commas and am telling CS that a new line with proper tabbing indicates a new attribute. Also there is no need for the opening and closing curly braces. CS knows that the object is done when it reaches destroy_rome() function that has the same amount of tabbing as the person variable.

So let’s take a look at how this helps out with a BackboneJS view that’s in a project using RequireJS.

  define [
    'jquery'
    'underscore'
    'backbone'
    'html2canvas'
    'models/map'
    'models/user'
    'views/maps/map_view'
    'views/users/sidebar'
    'hbars!templates/maps/map'
  ], ($, _, Backbone, html2canvas, Map, User, MapView, Sidebar, newMap) ->
    EditMap = Backbone.View.extend
      el: "#content section"

      events:
        "click area.region":     "highlight_region"

      initialize: (options) ->
        _.bindAll this, 'render'
        this.user = options.user
        this.empire_information = options.empire_information

      render: () ->
        this.$el.html newMap(this)
        options =
          parent: this
          allow_edit: true
          update_turn: false
        $.when(this.prepare_map()).then => @render_sidebar options
        this

      prepare_map: () ->
        this.$('#map_image').maphilight()
        this.selected = this.empire_information[this.user.get('empire')]
        $.each @empire_information, (key, attrs) =>
          additional = {color: attrs.color, border: attrs.border}
          @color_settlements region, additional for region in attrs.regions

    EditMap

and the mess that it compiles down into:

  (function() {

    define(['jquery', 'underscore', 'backbone', 'html2canvas', 'models/map', 'models/user', 'views/maps/map_view', 'views/users/sidebar', 'hbars!templates/maps/map'], function($, _, Backbone, html2canvas, Map, User, MapView, Sidebar, newMap) {
      var EditMap;
      EditMap = Backbone.View.extend({
        el: "#content section",
        events: {
          "click area.region": "highlight_region"
        },
        initialize: function(options) {
          _.bindAll(this, 'render');
          this.user = options.user;
          return this.empire_information = options.empire_information;
        },
        render: function() {
          var options,
            _this = this;
          this.$el.html(newMap(this));
          options = {
            parent: this,
            allow_edit: true,
            update_turn: false
          };
          $.when(this.prepare_map()).then(function() {
            return _this.render_sidebar(options);
          });
          return this;
        },
        prepare_map: function() {
          var _this = this;
          this.$('#map_image').maphilight();
          this.selected = this.empire_information[this.user.get('empire')];
          return $.each(this.empire_information, function(key, attrs) {
            var additional, region, _i, _len, _ref, _results;
            additional = {
              color: attrs.color,
              border: attrs.border
            };
            _ref = attrs.regions;
            _results = [];
            for (_i = 0, _len = _ref.length; _i < _len; _i++) {
              region = _ref[_i];
              _results.push(_this.color_settlements(region, additional));
            }
            return _results;
          });
        }
      });
      return EditMap;
    });

  }).call(this);

Looking at the two which one seems easier to write and maintain?

Looping

Looping in CoffeeScript was one of the trickier aspects that took me a while to wrap my brain around, but once I did it became one of my favorite features of CS. The most used looping method, (at least for me) is comprehensions. The best way to describe a comprehension is thinking of it more like the jQuery or Underscore each method. These each methods iterate through the array or object and run a function (often anonymous) on each of value.

From coffeescript.org

Most of the loops you'll write in CoffeeScript will be comprehensions over arrays, objects, and ranges. Comprehensions replace (and compile into) for loops, with optional guard clauses and the value of the current array index. Unlike for loops, array comprehensions are expressions, and can be returned and assigned.

CoffeeScript:

  generals = [‘Caesar’, ‘Hannibal’, ‘Alexander’, ‘Hanno’]  
  declare_title man for man in generals

  declare_title = (man) ->
     alert “#{man} the Great"

So what does all this mean? Well we have a function declare_title() that takes one parameter man. CS’s syntax boils down to for every man in the generals array, run declare_title() function and pass the current value.

It’s important to note that declare_title() can also return a value. So if we can mimic the Ruby map() method, which loops through an array or object and generates a new array/object with the altered version of each value.

  generals = [‘Caesar’, ‘Hannibal’, ‘Alexander’, ‘Hanno’]  
  generals_with_titles = declare_title man for man in generals

  console.log generals_with_titles

  declare_title = (man) ->
     “#{man} the Great”

The console.log will return [‘Caesar the Great’, ‘Hannibal the Great’, ‘Alexander the Great’, ‘Hanno the Great’].

There is a lot more to looping in CoffeeScript, a lot more than I will go into here, but definitely checkout From coffeescript.org#loops to get more information.

if… else… unless… oh my!

if/else statements in CoffeeScript like functions drop all curly braces and are whitespace dependent, but they also drop the parentheses.

So:

  if(name == ‘Hannibal’) {
     alert(name + “ is Carthaginian”);
  } else {
     alert(name + “ is from somewhere else”);
  }

Becomes:

  if name is ‘Hannibal’
     alert “#{name} is Carthaginian”
  else
     alert “#{name} is from somewhere else"

So what about else if? This is done just as you would expect:

  if name is ‘Hannibal’
     alert “#{name} is Carthaginian”
  else if name is ‘Caesar’
     alert “#{name} is Roman"
  else
     alert “#{name} is from somewhere else"

Pretty cut and dry considering what we’ve already learned with CS so far. Now let’s get into unless. If you’ve done any Ruby programing then the concept of unless will be an old hat to you. If not, never fear, it’s a pretty simple concept and extremely useful. unless is just the opposite of if. It runs the specified code if the statement tested returns false. Let’s take a look:

  unless name is ‘Caesar'
    alert “#{name} lived before the 1st century BC"

This compiles down into:

  if(name !== ‘Caesar’) {
     alert(name + "lived before the 1st century BC”);
  }

Unlike if statements you cannot use else in conjunction with unless.

Another nice feature about if and unless is that; if the test dependent code is only one line, you can throw the if/unless at the end of the line. Like so:

  rome = ‘empire’

  destroy_rome() if rome?
  expand_carthage_influence() unless rome?

One last note about if, else and unless, if you’re like me and really like the ternary method of if/else statements then you will be as disappointed as I was to find out that they do not exist in CoffeeScript. This is the one and only complaint I have with CS and one that I grind my teeth about often.

You can still do your if/else statement on one line, but it requires then and else operators to do so.

  greatest_general = if destroy_rome() then “Hannibal” else “Alexander"

A few things you may have noticed that I will go over in the next post.

  1. the is operator
  2. string interpolation
  3. the ? operator

I’m apparently going to cut this into yet another post, because the current one is getting quite long and I don’t want to go forever without posting something. Still to come in the next post: Additional operators, String Interpolation, Switch Statements, Function Binding and much more...

Spread the word
Tweet about this on TwitterShare on Google+Share on FacebookPin on PinterestShare on RedditShare on TumblrEmail this to someonePrint this page