Introduction to CoffeeScript

SCaLE 12x, February 23rd, 2014

My name is Dan Yoder

Goals

  • Not a tutorial, not a feature list
  • Give you a feel for using the language
  • Leave here wanting to learn more

Let's Get Started

This is a function:

-> "Hello World"

This is a function taking an argument:

(name) -> "Hello #{name}"

Let's give it a name:

hello = (name) -> "Hello #{name}"

Let's give it a name:

hello = (name) -> "Hello #{name}"

The variable hello is not a global.

Let's call it:

hello = (name) -> "Hello #{name}"
hello "Dan"
# > "Hello Dan"

CoffeeScript:

hello = (name) -> "Hello #{name}"
hello "Dan"

JavaScript:

var hello = function(name) {
  return "Hello " + name;
};
hello("Dan");

Factoid

You're Not Alone

As of January, 2014, CoffeeScript is the 18th most widely used programming language (RedMonk, 2014).

Fibonacci:

  fib = (n) ->
    if 0 <= n < 2 then n
    else if n >= 2
      fib(n-1) + fib(n-2)
    else undefined

Fibonacci:

  fib = (n) ->
    if 0 <= n < 2 then n
    else if n >= 2
      fib(n-1) + fib(n-2)
    else undefined

Try it out:

  fib(6)
  # > 8

Cool Feature Break No.1

Chained Comparisons

if 0 <= n < 2 then n

Try fib for larger values of n:

fib(40)

You'll be waiting for awhile.

Try fib for larger values of n:

fib(40)

You'll be waiting for awhile.

Problem: we keep recomputing the same results.

Solution: memoize results of prior function calls.

We'll write a function that memoizes the results of another function.

Brief Digression

The Y-combinator in CoffeeScript:

# λf.(λg.f (g g)) (λg.f (g g))
Y = (f) -> 
  ((g) -> f((x) -> g(g)(x)))((g) -> f((x) -> g(g)(x)))

# ß-reduction of Y: λf.(λg.g g) (λg.f (g g))
X = (f) -> 
  ((g) -> g((x) -> g(x)))((g) -> f((x) -> g(g)(x)))

# applied to factorial ... 
fact = X((f) -> (x) -> if x is 1 then 1 else x * f(x - 1))

## this returns 720
fact(6)

Now back to our Feature Presentation:

memoize = (fn) ->
  cache = {}
  (arg) ->
    cache[arg] ?= fn(arg)

Now we can memoize our fib function:

fib = memoize  (n) ->
  if 0 <= n < 2 then n
  else if n >= 2
    fib(n-1) + fib(n-2)
  else undefined

Now we can memoize our fib function:

fib = memoize  (n) ->
  if 0 <= n < 2 then n
  else if n >= 2
    fib(n-1) + fib(n-2)
  else undefined

Even fib(100) returns immediately.

Memoize

CoffeeScript:

memoize = (fn) ->
  cache = {}
  (arg) ->
    cache[arg] ?= fn(arg)

Memoize

JavaScript:

var memoize = function(fn) {
  var cache;
  cache = {};
  return function(arg) {
    var result;
    if ((result = cache[arg]) != null)
      result
    else
      cache[arg] = fn(arg);
  };
};

Cool Feature Break No.2

The Existential Operator

cache[arg] ?= fn(arg)
          ^^^^
  • ?: returns false if the operand is null or undefined.

  • ?=: conditional assignment, much like ||=, except safer.

A Queue

Adapted from The CoffeeScript Ristretto, by Reginald Braithwaite.

make_queue = ->
  do (array = []) ->
    enqueue: (value) -> array.push value
    dequeue: -> array.shift()
    length: -> array.length
    isEmpty: -> array.length is 0

Our queue features strict encapsulation.

queue = make_queue()
queue.enqueue 1
queue.enqueue 3
queue.length()
# > 2
queue.dequeue()
# > 1
queue.isEmpty()
# > false
queue.array
# > undefined

Cool Feature Break No.3

The do Statement

do (array = []) ->

Cool Feature Break No.3

The do Statement

do (array = []) ->
  • Immediately executes the passed function.
  • Can act as let-style binding clause
  • Also for escaping from closures within a loop

Classier Version

Proper JavaScript Classes

class Queue
  constructor: -> @_array = []
  enqueue: (value) -> @_array.push value
  dequeue: -> @_array.shift()
  length: -> @_array.length
  isEmpty: -> @_array.length is 0

Now we have real JavaScript classes, but less strict encapsulation. (That's probably okay.)

queue = new Queue
queue.enqueue 5
queue._array = []  # !!! uh-oh

Factoid

Not Just For The Browser

With the emergence of platforms like JXCore, CoffeeScript can be used to write native mobile, tablet, and desktop apps.

Extending classes:

class PriorityQueue extends Queue
  enqueue: (value) ->
    @_array.push value
    @_array.sort()

Feature Break No.4

Proper JavaScript Inheritance

CoffeeScript:

class Foo
class Bar            

Feature Break No.4

Proper JavaScript Inheritance

JavaScript:

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

Feature Break No.4

Proper JavaScript Inheritance

CoffeeScript:

class Foo
class Bar extends Foo

Feature Break No.4

Proper JavaScript Inheritance

JavaScript:

var Bar, Foo,
  __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; 
  };

Foo = (function() {
  function Foo() {}
  return Foo;
})();
Bar = (function(_super) {
  __extends(Bar, _super);
  function Bar() {
    return Bar.__super__.constructor.apply(this, arguments);
  }
  return Bar;
})(Foo);

Object literals are fun!

handle: "pirateking"
name:
  first: "Sterling"
  middle: "Malory"
  last: "Archer"
bio: "World's greatest secret agent"
feed: [
    "I swear I had something for this"
    "@lanakane do you not?"
    "Ocelots are honestly overrated"    
]

Real World Use

At PandaStrike, we've used CoffeeScript to build:

  • High-volume event logging system
  • WebRTC-based voice chat (www.glideroom.com
  • Text-based chat (www.glidechat.com)
  • Secure API for Bitcoin startup
  • Curated consumer health search engine

Real World Use

GlideChat

Loading a room:

discovery (client) ->
  {resources: {room}} = client
  room(href).get (error, response) ->
    unless error?
      roomGadget(response.resource)
    else
      if error.status is 404
        go "/missing?href=undefined"
      else
        console.log error

Feature Break No.5

Destructuring Assignment

Not just destructuring assignment, but nested destructuring assignment.

{resources: {room}} = client

Real World Use

GlideChat

Setting up DOM access with an object literal:

dom =
  title:
    display: $("h2.title")
    input: $(".title input")
  chat:
    display: $(".transcript article")
    input: $(".chat input")
  parties:
    display: $(".participants article")
  name:
    input: $(".nickname input")
  url:
    display: $(".url article")
  leave:
    button: $("a[href='#leave']")

Real World Use

GlideChat

Joining the room...

chat
  type: "event"
  content: "join guest"
  (response) ->
    {name} = response.resource
    dom.name.input.val(name)
    getRoom -> start()

Real World Use

GlideChat

Server-side, here's the handler for retrieving messages:

messages:
  get: (context) ->
    {match: {path, query}, request: {body}} = context
    room = Room.find(path)
    if room?
      id = timeout ->
        id = null
        respond context, last: query.after, messages: []
      room.receive(query)
      .once "success", (result) ->
        if id?
          clearTimeout(id)
          respond context, result
    else
      notFound(context, "That room doesn't seem to exist")

Why CoffeeScript?

  • Runs across many platforms: browser, server, and even native mobile, tablet, and desktop
  • Expressive, relatively easy for JavaScript developers to learn; or, for that matter, Python or Ruby developers
  • Addresses many of the "bad" parts of JavaScript

Myths

  • Difficult to debug
  • Incompatibilities with JavaScript
  • Ambiguous syntax, violates POLS

Recommended Resources

Introduction to CoffeeScript

SCaLE 12x, February 23rd, 2014

You can reach us at: