Yan Han's blog

On Computer Technology

23 Dec 2013

On CoffeeScript Mixins

CoffeeScript offers some rather nice improvements over JavaScript. One feature which I personally find very useful is the inclusion of classes. While JavaScript’s prototypal inheritance is powerful, I was exposed to more traditional OO languages prior to JavaScript, and I find that classes are a more intuitive way to organize code.

Chapter 3 of the Little Book on CoffeeScript offers a quick and dirty overview of classes in CoffeeScript. But that is not our main topic of the evening. Instead, we shall take a little look at Mixins, also explained in the same chapter.

Personally, I am rather new to the concept of Mixins. It was only through some Ruby work that I was first exposed to this idea. In short, Mixins are a way to mimick multiple inheritance. Now, before an angry mob of programmers kill me, I think we all agree that in general, multiple inheritance is a bad idea, but it does have its advantages. Mixins are a way of getting the best out of both worlds, gaining the power of multiple inheritance while keeping class hierarchies as a tree.

To understand Mixins, let us take a look at a rather contrived example in Ruby.

Suppose we have a ComputerMonitor class, which is a subclass of the Display class. However, we also want it to have the functionality of the BlackObject class (yea my monitors usually have black frames; I know this example is really contrived, but bear with me for a moment). How we will do it in Ruby is, instead of having BlackObject as a class, we organize it as a Module. So the ComputerMonitor class will extend the Display class, and include the BlackObject module, like this:

class ComputerMonitor < Display
  include BlackObject

We can immediately see several advantages of this approach, which makes use of Mixins:

And while I’m pretty new to this, I think my last point should be particularly true for Mixins. The code should be sufficiently generic to permit reuse across several classes, otherwise, we might as well put the code inside the class making use of it in the first place.

Since the above example is very contrived, here’s a more real world example. In Ruby, the Array and Hash classes are blueprints for the very indispensable data structures. When we do an ri Array, we see the following:

and when we do an ri Hash, we see the following:

See any similarities? They both include Enumberable, hence they share a lot of methods. An ri Enumerable confirms that yes, Enumerable is a Mixin:

So I hope that is sufficiently real world, and an ok introduction to Mixins. Now, onto how they are implemented in CoffeeScript.

Mixins in CoffeeScript

By default, CoffeeScript does not have Mixins as a built-in language feature. However, it is possible to implement them in CoffeeScript, as the Mixins section of Chapter 3 of the Little Book on CoffeeScript shows us.

That said, the full-fledged code for enabling Mixins in CoffeeScript is at the Extending classes section, which is right after the Mixins section. Here is the code:

moduleKeywords = ['extended', 'included']

class Module
  @extend: (obj) ->
    for key, value of obj when key not in moduleKeywords
      @[key] = value

    obj.extended?.apply(@)
    this

  @include: (obj) ->
    for key, value of obj when key not in moduleKeywords
      # Assign properties to the prototype
      @::[key] = value

    obj.included?.apply(@)
    this

We will use include for “instance level” Mixins, in other words, to mix in methods or properties we want to appear in every instantiated object of the class and its descendents. We will use extend for “class level” Mixins, which adds the methods or properties of the given object onto the class itself, which makes this kind of like static methods/attributes in C++ / Java.

The book provides an example usage:

classProperties =
  find: (id) ->
  create: (attrs) ->

instanceProperties =
  save: ->

class User extends Module
  @extend classProperties
  @include instanceProperties

# Usage:
user = User.find(1)

user = new User
user.save()

Here, we see that the User class inherits from Module. All the magic enabling Mixins is in the Module class, so having a class inheriting the Module class is a prerequisite to enabling Mixins.

Here, we see that the User class @extends the classProperties object. So the find and create methods are available on the User class itself (and descendent classes), but not User objects.

The User class @includes the instanceProperties object. As such, the save method is available on User objects, and objects which are instances of classes that descend from User.

From here, we can see that, for a class to use Mixins:

  1. It has to inherit from Module (or a class with similar functionality)
  2. Either use the @extend or @include, and pass in an object to those functions. That object will contain the methods / properties we want to mix in to the class. We can write the methods as if they belong to a class (meaning that we can use the @ notation)

Now then, I skipped some details that I wish to go back to. Let’s take a look at the code for the Module class again:

moduleKeywords = ['extended', 'included']

class Module
  @extend: (obj) ->
    for key, value of obj when key not in moduleKeywords
      @[key] = value

    obj.extended?.apply(@)
    this

  @include: (obj) ->
    for key, value of obj when key not in moduleKeywords
      # Assign properties to the prototype
      @::[key] = value

    obj.included?.apply(@)
    this

When I first looked at this, several questions came into mind:

  1. what is the use of the moduleKeywords variable?
  2. what are the obj.extended?.apply(@) lines doing?
  3. why use @[key] = value for @extend, but @::[key] = value for @include ?

All these questions can be answered by looking at the last example given in Chapter 3 of the Little Book on CoffeeScript:

ORM =
  find: (id) ->
  create: (attrs) ->
  extended: ->
    @include
      save: ->

class User extends Module
  @extend ORM

Essentially, this example is doing exactly the same thing as the example where we had the User class @extend the classProperties object and @include the instanceProperties object (so the User class will possess the find and create class methods, and the save instance method). But how can that be, they look so different. Yet it is truly the case. Let’s go into a bit more detail.

Let’s start from the definition of the User class:

class User extends Module
  @extend ORM

This should probably be familiar to us by now. It will call the @extend method of the Module class, with the ORM object passed in. Let’s look at how @extend is defined inside the Module class:

  @extend: (obj) ->
    for key, value of obj when key not in moduleKeywords
      @[key] = value

    obj.extended?.apply(@)
    this

our ORM object has the find, create and extended properties. There is something subtle with the name of the function. By writing it as @extend instead of extend, we are actually creating extend as a class method. Use of the js2coffee tool confirms this.

The @extend function is compiled to the following JavaScript:

  Module.extend = function(obj) {
    var key, value, _ref;
    for (key in obj) {
      value = obj[key];
      if (__indexOf.call(moduleKeywords, key) < 0) {
        this[key] = value;
      }
    }
    if ((_ref = obj.extended) != null) {
      _ref.apply(this);
    }
    return this;
  };

Changing @extend to extend causes the same snippet of code to be compiled to:

  Module.prototype.extend = function(obj) {
    var key, value, _ref;
    for (key in obj) {
      value = obj[key];
      if (__indexOf.call(moduleKeywords, key) < 0) {
        this[key] = value;
      }
    }
    if ((_ref = obj.extended) != null) {
      _ref.apply(this);
    }
    return this;
  };

Notice how everything stays the same, except that Module.extend becomes Module.prototype.extend. As such, since the function is defined as @extend, the @ refers to the Module class. This is why passing the an object to @extend will cause the object’s properties to be available to the target class.

In a similar spirit, for @include:

  @include: (obj) ->
    for key, value of obj when key not in moduleKeywords
      # Assign properties to the prototype
      @::[key] = value

    obj.included?.apply(@)
    this

the code is compiled to the following JavaScript:

  Module.include = function(obj) {
    var key, value, _ref;
    for (key in obj) {
      value = obj[key];
      if (__indexOf.call(moduleKeywords, key) < 0) {
        this.prototype[key] = value;
      }
    }
    if ((_ref = obj.included) != null) {
      _ref.apply(this);
    }
    return this;
  };

Notice how @::[key] = value is compiled to this.prototype[key] = value;. This is what makes the properties of the object available to the instances of the target class.

Going back to @extend:

  @extend: (obj) ->
    for key, value of obj when key not in moduleKeywords
      @[key] = value

    obj.extended?.apply(@)
    this

Recall that the ORM object we passed in to @extend has 3 properties: find, create and extended. So what the code here is doing is, for any key that is not in the moduleKeywords variable, make them available to the class.

It happens that moduleKeywords is defined as follows:

moduleKeywords = ['extended', 'included']

and we see that extended is in moduleKeywords, so that property is not added to the User class.

We are done with the for loop in the @extend method, so now we are at this intimidating line:

    obj.extended?.apply(@)

obj.extended? checks if obj has a property called extended. If so, then call its apply method, with this bounded to @. Taking another look at the ORM object we passed in to @extend:

ORM =
  find: (id) ->
  create: (attrs) ->
  extended: ->
    @include
      save: ->

We see that the ORM object has an extended property, which happens to be a function. In JavaScript (and hence CoffeeScript), Functions have the apply method (details here), which takes in 1 argument, and simply calls the function with this becoming that supplied argument.

In other words, obj.extended?.apply(@) will call the ORM.extended function, replacing this with @ (here, @ refers to the User class). Inside ORM.extended, we have an @include function call, with the argument being an object with the save property. I think it should be clear what this is doing - it is a roundabout way of @include‘ing an object with the save function to the User class. So hopefully this paragraph in Chapter 3 of The Little Book on CoffeeScript makes sense now, especially with regards to the callbacks:

As you can see, we’ve added some static properties, find() and create() to the User class, as well as some instance properties, save(). Since we’ve got callbacks whenever modules are extended, we can shortcut the process of applying both static and instance properties:

Pretty neat, huh? I didn’t figure all that out by myself. All the credit goes to these 2 questions on Stack Overflow, and their answers:

This post is really just an organization of the wonderful chapter inside The Little Book of CoffeeScript, and knowledge gained from the 2 questions above. Hopefully there aren’t too many mistakes. Haha

Disclaimer: Opinions expressed on this blog are solely my own and do not express the views or opinions of my employer(s), past or present.

comments powered by Disqus