Private members in CoffeeScript

In short: stick to the JavaScript convention of prefixing private members with _ because private members don't work very well.

CoffeeScript can implement private functions, but not other variables. It's a big can of worms and is only elegant in very simple cases. I'll warn you: this gets hairy, and I'd recommend using the JavaScript convention of prefixing private members with _ and calling it a day.

Simple functions

In simple cases for functions, it works very well. Take a look at the example below:

class Person
  sayHello = (to) ->  # Private
    console.log "Hello, #{to}."
  helloWorld: ->
    sayHello("world")

p = new Person
p.helloWorld()  # => "Hello, world."

This works just as expected, but it only works for functions that don't talk to this. We'll explore how to get things talking to this below.

This fails with non-functions

Unfortunately, we can't have private variables. Take a look at this example:

class Animal
  firstName = ""  # Private member
  constructor: (n) ->
    firstName = n
  getFirstName: ->
    firstName

jambo = new Animal "Jambo"
console.log jambo.getFirstName()  # => "Jambo"

birch = new Animal "Birch"
console.log birch.getFirstName()  # => "Birch"

console.log jambo.getFirstName()  # => "Birch"  # Not what we want!!

If you take a look at the compiled JavaScript, the problem becomes more evident -- the firstName variable is "shared" across all instances of the Animal class. Unfortunately, CoffeeScript can't fix this issue for us.

Technically, this behavior is the same for functions, but functions don't really get redefined, so it's okay. If you're ever redefining your functions, then it's dangerous.

Many thanks to Harry Brundage for pointing this out! I had this wrong.

Getting functions to work

It's likely that you'll want to have a private function that talks to a class's instance variables. Unfortunately, this won't work:

class Sorcerer
  constructor: (@spell) ->
  conjureSpell = ->   # private
    @spell.conjure()  # "this" is scoped incorrectly here, so it won't work
  useSpell: ->
    conjureSpell()
    @spell.use()

s = new Sorcerer
  conjure: -> console.log "Brewing potion..."
  use: -> console.log "Now I have superpowers!"

s.useSpell()  # Cannot call method "conjure" of undefined

Even if we define conjureSpell with CoffeeScript's fat arrow, it doesn't work. This is because @spell in conjureSpell is undefined -- the scope of this is wrong. There are a few ways around this problem, but none of them are very pretty.

They're private, not protected

Private members are just that: private. As such, we can't access private members in child classes:

class Person
  sayHello = (to) ->  # Private
    console.log "Hello, #{to}."
  helloWorld: ->
    sayHello("world")

class Employee extends Person
  helloBoss: ->
    sayHello("boss")

e = new Employee
e.helloBoss()  # => ReferenceError: sayHello is not defined

As far as I know, you can't make protected members with CoffeeScript classes because JavaScript doesn't really have classical inheritance. If you need them, you'll need to make them public and prefix them with an underscore.

Some concluding notes

If this has shown you anything, it's that private members in CoffeeScript are pretty weird, and it's probably easier to prefix your private variables with an underscore.