Delegetter

Jan 20, 2014

It's pretty common in the OO world for one object to delegate stuff down to another object. For example, in Jinn, I'm decorating cameras. Though each camera decorator has its own behaviour - say, staying within some bounds - it'll delegate to the camera it decorates for things like the position and the viewing dimensions.

In a less powerful language, setting up delegation often means a lot of boilerplate:

class Delegate
	delegatedProperty: "delegate property"

class Delegater
	myDelegate: new Delegate

	Object.defineProperty @prototype,
		"delegatedProperty",
		get: ->
			@myDelegate.delegatedProperty

		set: (value) ->
			@myDelegate.delegatedProperty = value

delegater = new Delegater
console.log delegater.delegatedProperty
# => "delegate property"

How awful would it be if every time you want to delegate a property, you had to write that same chunk of code? Thankfully, JavaScript lets us do some really horrific cool things and CoffeeScript gives us a nice way of writing them.

On The Right Syntracks

For something like this, I think the right way to go is to pin down the syntax and then, y'know, make it happen. Don't sweat the implementation, just focus on what it is you're trying to say.

What we want to be able to do is declare the delegates and the properties we're delegating to them. Should look something like this:

class Delegater
	myDelegate: new Delegate

	@delegate
		myDelegate: ["delegatedProperty"]

If we wanted to set up more delegations, we'd get something like this:

class Foo
	myBar:	new Bar
	myBaz:	new Baz

	@delegate
		myBar:	["bar"]
		myBaz:	["baz", "moreBaz"]

Simple enough, right? Let's make it happen.

Delegreatness

CoffeeScript's class definition is actually hella nice. We can execute arbitrary expressions and this refers to the constructor function. That opens the language up to simple, but powerful metaprogramming.

What we're gunna do is add a function to the Function prototype called delegate. This'll take the delegation specs and set them up. Because it's attached to the Function prototype, we can call call delegate from the class definition with @delegate, just like we wanted.

Function::delegate = (specs) ->
	# make it happen

To call this, we pass it the specs. This is just a JavaScript object, but CoffeeScript's syntax makes this incredibly clean. With parens, it'd look more like this:

class Foo
	myBar:	new Bar
	myBaz:	new Baz

	@delegate({
		myBar:	["bar"],
		myBaz:	["baz", "moreBaz"]
	})

Now we can move the boilerplate stuff into the delegate function, iterating over the specs to define our property delegations. Since JavaScript let us do string-based property access with the [] notation, this is just as straightforward as you could hope:

Function::delegate = (specs) ->
	for delegate, properties of specs
		for property in properties
			do (delegate, property) =>
				Object.defineProperty @prototype,
					property,
					get: ->
						this[delegate][property]
					set: (val) ->
						this[delegate][property] = val

And everything just works:

class Delegater
	myDelegate: new Delegate

	@delegate
		myDelegate:	["delegatedProperty"]

delegater = new Delegater
console.log delegater.delegatedProperty
# => "delegate property"

Pretty rad, right? And you could take this even further. For example, set up a dot syntax for delegation to nested objects or use a syntax like {from: "property", to: "delegatedProperty"} to map between properties with different names.

The world's your syntactic oyster, baby. Figure out a syntax and make it happen or, better yet, delegate the work to someone else.