CoffeeScript – Refining Your Pallet with Sugar – Functions



CoffeeScript – Refining Your Pallet with Sugar – Functions

0 0


coffeescript-talk

My talk for the Suncoast.js meetup

On Github tylerjohnst / coffeescript-talk

CoffeeScript

Refining Your Pallet with Sugar

Created by Tyler Johnston / @tylerjohnst

I'm Tyler Johnston

  • Ruby on Rails Developer
  • Javascript Developer
  • Lover
  • You can find me on the Twitters: @tylerjohnst

Coffeescript? Wut?

  • Language built on top of Javascript, with Javascript, to make Javascript less painful.
  • Compiles to near one to one code to make debugging the compiled code very easy.
  • Implements and standardizes common idioms that people have been using for a while now.
  • Adds wonderful syntatic sugar pulling the best things from popular dynamic languages like Python and Ruby.

It only adds sugar?

Yes! Because sugar is awesome!

Ok, you've convinced me! Now what?

CoffeeScript requires just a few simple steps to get going:

  • brew install node
  • sudo npm install -g coffee-script
  • coffee -c myfile.coffee

Two major changes are going to trip up everyone getting started with Coffeescript. Functions and significant whitespace. After that, it’s just Javascript.

Functions

  # CoffeeScript
  titleize = (string) ->
    string.charAt(0).toUpperCase() + string.slice(1)
          
  // Javascript
  var titleize;

  titleize = function(string) {
    return string.charAt(0).toUpperCase() + string.slice(1);
  };
            

Significant Whitespace

CoffeeScript uses this to delimit blocks of code. Semicolons are still valid, useful for multiple functions on the same line but are completely optional.
  # CoffeeScript
  $('a[data-tooltip]').tooltip
    position:
      my: "center bottom-20"
      at: "center top"
      using: (position, feedback) ->
        $(this).css(position)
          
  // Javascript
  $('a[data-tooltip]').tooltip({
    position: {
      my: "center bottom-20",
      at: "center top",
      using: function(position, feedback) {
        return $(this).css(position);
      }
    }
  });
          

Wait, I thought this was supposed to be all fun and happiness?! This looks like more work!

Bring me the syntatic sugar!

Strings!

String manipulation is one of the weakest aspects of Javascript for me. CoffeeScript brings it to the 21st century with Ruby style string concatenation.

  name   = 'Tyler'
  age    = 26
  height = 71

  "My name is #{name}, I'm #{age} years old, and I'm #{height} inches tall."
            
  var age, height, name;
  name = 'Tyler';
  age = 26;
  height = 71;

  "My name is " + name + ", I'm " + age + " years old, and I'm " + height + " inches tall.";
            

Block Strings

Block strings can be used to hold formatted or indentation-sensitive text. The indentation level that begins the block is maintained throughout.

  html = """
         <strong>
           cup of coffeescript
         </strong>
         """
            
  var html;

  html = "<strong>\n  cup of coffeescript\n</strong>";
            

Functions

Short hand syntax with (arg) ->

Single Line Functions

  square = (x) -> x * x
  cube   = (x) -> square(x) * x
            
  var cube, square;

  square = function(x) {
    return x * x;
  };

  cube = function(x) {
    return square(x) * x;
  };
            

Fat Arrow

Tame the context of this!

  Account = (customer, cart) ->
    @customer = customer
    @cart = cart

    $('.shopping_cart').bind 'click', (event) =>
      @customer.purchase @cart
            
  var Account;

  Account = function(customer, cart) {
    var _this = this;
    this.customer = customer;
    this.cart = cart;
    return $('.shopping_cart').bind('click', function(event) {
      return _this.customer.purchase(_this.cart);
    });
  };
            

Fat Arrow Cont.

If we had used -> in the callback above, @customer would have referred to the undefined "customer" property of the DOM element, and trying to call purchase() on it would have raised an exception.

When used in a class definition, methods declared with the fat arrow will be automatically bound to each instance of the class when the instance is constructed.

Default Arguments

  animalCall = (sound = 'moooooo!') ->
    console.log(sound)
            
  var animalCall;

  animalCall = function(sound) {
    if (sound == null) {
      sound = 'moooooo!';
    }
    return console.log(sound);
  };
            

Splats

  animalCall = (sound = 'moooooo!', animals...) ->
    console.log sound, animals.join(', ')

  animalCall('bark', 'dog', 'frog', 'cat')
            
  var animalCall,
    __slice = [].slice;

  animalCall = function() {
    var animals, sound;
    sound = arguments[0], animals = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
    if (sound == null) {
      sound = 'moooooo!';
    }
    return console.log(sound, animals.join(', '));
  };

  animalCall('bark', 'dog', 'frog', 'cat');
            

Conditionals

Lots of sugar here inspired by Ruby:

  # CoffeeScript
  improveMood() if singing

  if happy and knowsIt
    clapsHands()
    chaChaCha()
  else
    showIt()

  date = if friday then sue else jill
            
  var date, mood;

  if (singing) {
    improveMood();
  }

  if (happy && knowsIt) {
    clapsHands();
    chaChaCha();
  } else {
    showIt();
  }

  date = friday ? sue : jill;
            

Loops and Comprehensions

Single line iterators. All of the sugar makes my teeth hurt!

  # Eat lunch.
  eat(food) for food in ['toast', 'cheese', 'wine']

  # Fine five course dining.
  courses = ['greens', 'caviar', 'truffles', 'roast', 'cake']
  menu(i + 1, dish) for dish, i in courses

  # Health conscious meal.
  foods = ['broccoli', 'spinach', 'chocolate']
  eat(food) for food in foods when food isnt 'chocolate'
            
  var courses, dish, food, foods, i, _i, _j, _k, _len, _len1, _len2, _ref;

  _ref = ['toast', 'cheese', 'wine'];
  for (_i = 0, _len = _ref.length; _i < _len; _i++) {
    food = _ref[_i];
    eat(food);
  }

  courses = ['greens', 'caviar', 'truffles', 'roast', 'cake'];

  for (i = _j = 0, _len1 = courses.length; _j < _len1; i = ++_j) {
    dish = courses[i];
    menu(i + 1, dish);
  }

  foods = ['broccoli', 'spinach', 'chocolate'];

  for (_k = 0, _len2 = foods.length; _k < _len2; _k++) {
    food = foods[_k];
    if (food !== 'chocolate') {
      eat(food);
    }
  }
            

Works with Javascript Objects as well!

The key difference between array and object looping is the operator. Use of for objects and in for arrays since arrays are just objects under the hood.

  yearsOld = max: 10, ida: 9, tim: 11

  ages = for child, age of yearsOld
    "#{child} is #{age}"
            
  var age, ages, child, yearsOld;

  yearsOld = {
    max: 10,
    ida: 9,
    tim: 11
  };

  ages = (function() {
    var _results;
    _results = [];
    for (child in yearsOld) {
      age = yearsOld[child];
      _results.push("" + child + " is " + age);
    }
    return _results;
  })();
            

Operators

CoffeeScript redefines a few Javascript operators that either don't work as expected or as shorthands.

Operators

As well as word operators, there are some other defaults:

  • == converts to ===
  • != converts to !==

Classes

Be more OOP with CoffeeScript!

  # CoffeeScript
  class StringEditor
    initialize: (@string) ->

    truncate: (length = 30) ->
      if @string.length > length
        sliced = @string.slice(0, length)
        "#{sliced}..."
      else
        @string

  new StringEditor('foobar')
            
  // Javascript
  var StringEditor;

  StringEditor = (function() {
    function StringEditor() {}

    StringEditor.prototype.initialize = function(string) {
      this.string = string;
    };

    StringEditor.prototype.truncate = function(length) {
      var sliced;
      if (length == null) {
        length = 30;
      }
      if (this.string.length > length) {
        sliced = this.string.slice(0, length);
        return "" + sliced + "...";
      } else {
        return this.string;
      }
    };

    return StringEditor;

  })();

  new StringEditor('foobar');
            

Subclasses

  # CoffeeScript
  class BetterStringEditor extends StringEditor
    truncate: (length = 15) ->
      super(string)
            
  var BetterStringEditor, _ref,
    __hasProp = {}.hasOwnProperty,
    __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };

  BetterStringEditor = (function(_super) {
    __extends(BetterStringEditor, _super);

    function BetterStringEditor() {
      _ref = BetterStringEditor.__super__.constructor.apply(this, arguments);
      return _ref;
    }

    BetterStringEditor.prototype.truncate = function(length) {
      if (length == null) {
        length = 15;
      }
      return BetterStringEditor.__super__.truncate.call(this, length);
    };

    return BetterStringEditor;

  })(StringEditor);