Coffee Tawk

Most people who claim to really love JavaScript tend to spend an inordinate amount of time trying to make it look more like any other language. Slapping oddball DSLs on top of JavaScript is such a popular path to open source fame these days that we’ve now got full-blown cross compilers like CoffeeScript taking that old school shit to places Netscape never dreamed of. Whether this is a good thing or a bad thing is something that JavaScript nerds can argue about for hours on end, but I think we can all agree that it is at least a thing and, if you write web software, odds are good it’s also a thing you’re gonna encounter at some point.

One of the first things most DSLs do is try to simulate classical inheritance, and CoffeeScript is no different. It gives us a nifty class B extends A syntax that we all know and love from many other languages and lets us declare our class’ methods and properties in a succinct way that can be quite a bit easier to read. Unfortunately, it still just compiles back into plain old JavaScript at the end of the day, and those classes you think you’re writing are totally fake. Most of the time you can just close your eyes and lose yourself in the beautiful illusion, but I’ve found one thing that snaps me out of that fever dream repeatedly: Class variables and static class variables are both declared the exact same way, and you don’t get a choice in the matter.

A simple demonstration:

class Parent extends Object scalarProperty: 1 collectionProperty: [1, 2, 3] class Child extends Parent anotherProperty: 2 p = new Parent() c = new Child() c.scalarProperty++ c.collectionProperty.push(4) console.log(c.scalarProperty) console.log(c.collectionProperty) console.log(p.scalarProperty) console.log(p.collectionProperty)

Parent declares two properties, one a primitive scalar, the other an array. CoffeeScript dutifully turns both of these declarations into assignments to Parent.prototype, so you’d probably expect them to both behave the same way. If you run the code however, you can see that any alteration of the Child instance’s scalarProperty affects only instances of that class, while alterations of the collectionProperty affect all instances of both Parent and Child.

2 [ 1, 2, 3, 4 ] 1 [ 1, 2, 3, 4 ]

This happens because all that extends really does for you is perform a shallow copy of one object’s values to another. That means it makes a series of assignments like Child.p = Parent.p and spits out your new object. In JavaScript, primitive scalars are always passed by value, while objects and arrays are always passed by reference, so you end up with this wildly different behavior being triggered by the same fucking syntax. Supergood.

So what do you do if you want to have an array that’s both inheritable and mutable without side-effects? Deep copy it yourself.

For a less abstract example, let’s say we’re building some View classes. Each View instance will require that some stylesheets be loaded into the browser, and some of them may very well require the same stylesheets as others. It makes sense to avoid repeating ourselves, so we want to put a list of common stylesheets into a base class which children can inherit and extend with their own, additional requirements. Ideally, we’d do this declaratively at the top of our class the same way we do our other properties.

The goal then, is something like this:

class BaseView extends Object requirements: ["1.css", "2.css"] class SpecificView extends BaseView requirements: ["1.css", "2.css", "3.css", "4.css"]

Obviously, that will never work; one class’ requirements will simply overwrite the other’s rather than inherit and extend, but that’s the target we’re trying to hit. What we really need to do is change the behavior of the extends keyword to do what we want it to, but forking CoffeeScript is more work than we have time for. We pretty much have to write our own methods, but CoffeeScript doesn’t vend any hooks for us to patch custom logic into.

Hows about a simple mutator method that manages an empty array declared in the base class? Something like:

class BaseView extends Object require: (filename) -> @requirements ?= [] @requirements.push(filename)

We would then be able to invoke it fairly succinctly from either class as it’s being built with a bit of dancy syntax:

class SpecificView extends BaseView @::require("3.css") @::require("4.css")

We’re now in mindfuck territory, so look at what CoffeeScript compiles that into.

SpecificView = (function() { __extends(SpecificView, BaseView); function SpecificView() { SpecificView.__super__.constructor.apply(this, arguments); } SpecificView.prototype.require("3.css"); SpecificView.prototype.require("4.css"); return SpecificView; })();

Inside the SpecificView constructor, this refers to SpecificView itself, not an instance of a SpecificView, but we can still invoke its inherited require method without an instance by digging down into its prototype. Neat, but we’re still just mucking up a shared array because CoffeeScript copies it by reference in __extends. Like Shrek always says: Life is piss, Donkey.

But hey, we can tell if two arrays are really the same thing, right? Let’s make that BaseView class do some more work.

class BaseView extends Object require: (filename) -> @requirements ?= [] if !@hasOwnProperty("requirements") @requirements = @requirements.slice() @requirements.push(filename)

Now our class’ prototype has its own copy of the parent’s requirements to mutilate as it sees fit without affecting the parent or any other descendant classes. It’s now safe to push from a descendant.

All together, the final solution is probably no speed demon, but it is both declarative and easy to follow.

class BaseView extends Object require: (filename) -> @requirements ?= [] if !@hasOwnProperty("requirements") @requirements = @requirements.slice() @requirements.push(filename) class SpecificView extends BaseView @::require("1.css") @::require("2.css") class MoreSpecificView extends SpecificView @::require("3.css") @::require("4.css") console.log(new SpecificView().requirements) console.log(new MoreSpecificView().requirements) Your output should look something like [ '1.css', '2.css' ] [ '1.css', '2.css', '3.css', '4.css' ]

BOOSH! Slideshow; you’re in it.

CoffeeScript is probably making the right choice in performing shallow copies by default—deep copies are more expensive and the threat of infinite recursion is always looming overhead—but it’d be nice if it threw you some kind of syntactical bone that let you say Hey, deep copy this property for me so I don’t shoot myself wondering why my code doesn’t fucking work. Maybe take another page from Ruby’s book and do the whole @@ thing. The project’s maintainers seem to disagree, though, so I wouldn’t hold my breath.

Special thanks to GitHub user satyr for pointing out a bumbling oversight in my initial implementation.