Skip to content
This repository
branch: master
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 157 lines (119 sloc) 6.957 kb

Encapsulating State in CoffeeScript

(click here for examples in JavaScript)


OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.--Alan Kay

In CoffeeScript, an object is fundamentally the same thing as what other programming languages call (variously) maps, dictionaries, records, structs, frames, or what-have-you. Sure, there is some support for prototypes and there is some support for building what look like classes. But at their heart, CoffeeScript objects are simple yet flexible and powerful.

As an example of how powerful they can be without the extra support for "classes," we're going to look at encapsulation using CoffeeScript's objects. We're not going to call it "object-oriented programming," mind you, because that would start a long debate. This is just plain encapsulation(1), with a dash of information-hiding.

bundling functions with data

The key characteristic of encapsulation is bundling data with functions that operate on the data. Consider a stack. There are three basic operations: pushing a value on, popping a value off, and testing to see whether the stack is empty or not.

We can create a stack quite easily with an object containing an array and an index(2):

stack =
  array: []
  index: -1
push = (value) ->
  stack.array[stack.index += 1] = value
pop = ->
  do (value = stack.array[stack.index]) ->
    stack.index -= 1 if stack.index >= 0
    value
isEmpty = ->
  stack.index < 0

Bundling the functions with the data does not require any special "magic" features. CoffeeScript objects can have elements of any type, including functions:

stack = do (obj = undefined) ->
  obj =
    array: []
    index: -1
    push: (value) ->
      obj.array[obj.index += 1] = value
    pop: ->
      do (value = obj.array[obj.index]) ->
        obj.index -= 1 if obj.index >= 0
        value
    isEmpty: ->
      obj.index < 0

stack.isEmpty()
  #=> true
stack.push('hello')
  #=> 'hello'
stack.push('CoffeeScript')
 #=> 'CoffeeScript'
stack.isEmpty()
  #=> false
stack.pop()
 #=> 'CoffeeScript'
stack.pop()
 #=> 'hello'
stack.isEmpty()
  #=> true

hiding state

Our stack does bundle functions with data, but it doesn't hide its state. "Foreign" code could interfere with its array or index. So how do we hide these? We already have a closure, let's use it:

stack = do (array = [], index = -1) ->
  push: (value) ->
    array[index += 1] = value
  pop: ->
    do (value = array[index]) ->
      array[index] = undefined
      index -= 1 if index >= 0
      value
  isEmpty: ->
    index < 0

stack.isEmpty()
  #=> true
stack.push('hello')
  #=> 'hello'
stack.push('CoffeeScript')
 #=> 'CoffeeScript'
stack.isEmpty()
  #=> false
stack.pop()
 #=> 'CoffeeScript'
stack.pop()
 #=> 'hello'
stack.isEmpty()
  #=> true

We don't want to repeat this code every time we want a stack, so let's make ourselves a "stack maker:"

StackMaker = ->
  do (array = [], index = -1) ->
    push: (value) ->
      array[index += 1] = value
    pop: ->
      do (value = array[index]) ->
        array[index] = undefined
        index -= 1 if index >= 0
        value
    isEmpty: ->
      index < 0

stack = StackMaker()

The stack maker is nothing more than a function that returns a new stack for us. Now we can make stacks freely, and we've hidden their internal data elements. We have methods and encapsulation, and we've built them out of CoffeeScript's fundamental functions and objects. No extra language support is required, because basic objects, functions, and closures are powerful features that can be combined to build almost any data structure we require.

question: is encapsulation "object-oriented?"

We've built something with hidden internal state and "methods," all without needing special def or private keywords. Mind you, we haven't included all sorts of complicated mechanisms to support inheritance, mixins, and other opportunities for debating the nature of the One True Object-Oriented Style on the Internet.

So some would say "Yes," while others would say, "Perhaps technically, but not as most programmers expect, so it's No Damn Good." Then again, the key lesson experienced programmers repeat (although it often falls on deaf ears) is, Composition instead of Inheritance.

So maybe we aren't missing much.

(Discuss on hacker news and /r/coffeescript. This essay appears in slightly different form in the upcoming book CoffeeScript Ristretto.)


Notes

  1. Encapsulation: "A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data."--Wikipedia
  2. Yes, there's a far superior way to track the size of the array, but we don't need it to demonstrate encapsulation and hiding of state, and this gives us an excuse to demonstrate how to manage complex state involving multiple values.

My recent work:

JavaScript AllongéCoffeeScript RistrettoKestrels, Quirky Birds, and Hopeless Egocentricity


(Spot a bug or a spelling mistake? This is a Github repo, fork it and send me a pull request!)

Reg Braithwaite | @raganwald

Something went wrong with that request. Please try again.