topics: functional programming, concurrency, web-development, REST, dynamic languages

Thursday, February 21, 2008

JavaScript parasitic inheritance, power constructors and instanceof.

Abstract. This posting shows how one can make Crockford's power constructor functions play nicely with the JavaScript keyword 'instanceof.'

[update: March 18, 2008. I asked Crockford what he thinks about this pattern, and he actually discourages the use of 'instanceof' -- instead, he prefers to "... rely instead on good design and polymorphism."]

The inheritance model of JavaScript is based on a combination of the 'new' keyword and the prototype property of (constructor) functions. JavaScript Guru Douglas Crockford (aka 'Yoda') argues that this model (which he calls pseudo-classical) is awkward. Instead, he proposes an elegant, powerful and simple model (parasitic inheritance), using so-called power constructor functions. Note, familiarity with the above concepts is necessary for complete understanding of this post [and something that every web developer should know anyway ;-)].

The advantages of power constructor functions include support for private, shared and public variables as well as simplicty (avoiding new and prototype). There is a mismatch, however, between constructors and the JavaScript keyword, instanceof. Consider the following example:

//recall that the object function creates a new object which has
//the input object, o, as its prototype
var object = (function() {
function F() {}
return function(o) {
F.prototype = o;
return new F();
};
})();//included for completeness.
var OriginalPerson = {
sayHello: function(){
return "Hello, my name is "+this.getName();
},
getName: function(){return 'Adam';}
};

function Person(name) {
var p = object(OriginalPerson);
p.getName = function() {return name;};
return p;
}

function Guru(name,topic) {
var g = object(Person(name));//Technically we don't need object(.) here
g.getTopic = function() {return topic;};
return g;
}

var karl = Person('Karl');
var crockford = Guru('Douglas','JavaScript');

karl instanceof Person;//<- false
crockford instanceof Guru;//<- false

Hmm... Clearly, any environment that makes crockford instanceof Guru evaluate to false must be getting something wrong!

In general, one has to do something to make super-constructors work with instanceof. The expression exp1 instanceof exp2 where exp1,exp2 are JS expressions (exp2 must evaluate to a function and exp1 should evaluate to an object) works with following semantics:

First exp1 is evaluated, say, to o then
exp2 is evaluated, say, to f. If o is an object and f is a function, the entire expression evaluates to true only if following o's [[prototype]] chain, we can reach f.prototype.

This means that to make this work, we must ensure that the object created has Person.prototype or Guru.prototype in its prototype chain.

What I would really like to end up with at the end of this blog entry, is to be able to write code similar to:

var OriginalPerson = {
sayHello: function(){
return "Hello, my name is "+this.getName();
},
getName: function(){return 'Adam';}
};

var Person = OriginalPerson.parasite(function(Host,name) {
var p = object(Host());
p.getName = function() {return name;};
return p;
});

var Guru = Person.parasite(function(Host, name,topic) {
var g = object(Host(name));
g.getTopic = function() {return topic;};
return g;
});

Guru('Douglas Crockford','JavaScript') instanceof Guru;//<-- true
Guru('Douglas Crockford','JavaScript') instanceof Person;//<-- true


The extra parameter Host is supposed to represent the "Host" of the parasite (i.e., Person in the case of Guru), the idea being that the parasite function will somehow 'wrap' the Person function to set it up so that 'instanceof' works, and then finally feed this wrapped function to the parasite (via the Host variable). I won't be able to write the code exactly as above, but we will get close... Anyway, hopefully this will make more sense very soon!

We will get there in two steps. First we code it up manually (so to speak) and secondly we will do the meta-programming with parasite.

Incidentally, we can exploit Crockford's 'shared secrets' technique to get the prototype chain working. Consider the following code.

function Person(name, proto) {
var p = object(proto || Person.prototype);
p.getName = function() {return name;};
return p;
}
Person.prototype = {
sayHello: function(){
return "Hello, my name is "+this.getName();
},
getName: function(){return 'Adam';}
};
Person('Karl') instanceof Person;//<-- true

The key here is the statement: object(proto || Person.prototype). This ensures that the object created has Person.prototype in its [[prototype]] chain. The proto ||-part is intended to be used as a 'shared secret' between a parasite 'sub type'/'sub power constructor'; the invariant is that proto || Person.prototype will always denote an object which is Person.prototype or has it in its prototype chain. It can be used as follows:

function Guru(name,topic,proto) {
var g = object(Person(name, proto || Guru.prototype));
g.getTopic = function() {return topic;};
return g;
}
Guru.prototype = object(Person.prototype);
Guru('Douglas Crockford','JavaScript') instanceof Guru;//<-- true
Guru('Douglas Crockford','JavaScript') instanceof Person;//<-- true

Actually, I feel some pleasure in the assignments:

Person.prototype = {
sayHello: function(){
return "Hello, my name is "+this.getName();
},
getName: function(){return 'Adam';}
};
and Guru.prototype = object(Person.prototype);: Intutively, these objects are the 'prototypes' of the objects created by the power constructors, so it feels natural to make this assignment.

So far so good - we have instanceof working with power-constructors, but we can do better. The problem now is to do the meta-programming that will handle the 'secret-sharing' behind the scenes.

In this example, we will enhance Object.prototype and Function.prototype. For simplicity I've introduced the constraint that all power-constructor functions should take only one argument [however, I'm convinced this can be generalized to more than one argument].

Note, slightly off topic here...
In either case, I've developed at taste for single-argument functions. Consider:

/** power-constructer for Guru objects
*@param conf {Object} a configuration object with properties:
* name and topic
* @return a Guru object with name: conf.name and topic: conf.topic.
*/
function Guru(conf){
var g = object(Person(conf)),
t = conf.topic || 'none';
g.getTopic = function(){return t;};
return g;
}
Guru({name:'Yoda', topic:'JavaScript'});

The disadvantage is that there is slightly more code to write. The advantages are (1) readability: Guru({name:'Yoda', topic:'JavaScript'}) can be read without having to consult the constructor function about which argument is the name and which is the topic; (2) optional/default arguments: you can leave out either of the arguments: Guru({name:'Yoda'}) or Guru({topic:'JavaScript'}) are both valid (in the multiple arg constructor with name as the first parameter, you'd have to write Guru('Yoda') (which is fine), and Guru(undefined,'JavaScript') (which is not).

Back on track
The following is my implementation. I extend Object.prototype and Function.prototype so that we can write:

var OriginalPerson = {
sayHello: function(){
return "Hello, my name is "+this.getName();
},
getName: function(){return 'Adam';}
};

var Person = OriginalPerson.parasite(function(Host, conf) {
var p = object(Host()),
name = conf.name || 'Anonymous';
p.getName = function() {return name;};
return p;
});

var Guru = Person.parasite(function(Host, conf) {
var g = object(Host(conf)),
topic = conf.topic || 'none';
g.getTopic = function() {return topic;}
return g;
});
var h = Guru({
name: 'Douglas Crockford',
topic: 'JavaScript'
});
h instanceof Guru;//<-- true
h instanceof Person;//<-- true


I've implemented it as follows (with comments):

Object.prototype.parasite = function(parasite){
/* This is the function returned as the result
of this call; it represents a wrapper for the
function in parameter parasite. wrapper will simply
call the parasite function, but supplying a Host function
as the first argument. If wrapper is called with proto === undefined
then the Host function will create an object with its prototype === this,
otherwise an object with prototype === proto is created (this lets
sub-parasites supply the proto parameter).
*/
function wrapper(conf,proto) {
var p = proto;//Exercise why is this necessary?
Array.prototype.splice.call(arguments,0,0,function(){
return object(p || wrapper.prototype);
});
return parasite.apply(this, arguments);
}
/* it is important that wrapper.prototype is set to this object, both so that
o instanceof wrapper works, and so that objects created with
object(p || wrapper.prototype) above will inherit properties of this */
wrapper.prototype = this;
return wrapper;
};
Function.prototype.parasite = function(parasite) {
var host_cons = this;//the constructor function for the host of parasite

/* Again, this function is the result of the computation.
When called it splices a Host function on the 0'th pos in the arguments array.
The Host function will call the host_cons and (important!) supplies an
additional last argument (proto). If proto === undefined we are in the case
where client code is calling wrapper, so we call the host_cons function
supplying wrapper.prototype; if instead proto is provided we call host_cons
with this object (this is the case where wrapper is called by a sub-parasite).
*/
function wrapper(conf,proto) {
var wrapper_this = this,
p = proto;//exercise: why?
Array.prototype.splice.call(arguments,0,0, function() {
Array.prototype.splice.call(arguments,arguments.length,0,
p || wrapper.prototype);
return host_cons.apply(wrapper_this,arguments);
});
return parasite.apply(this, arguments);
}
/* our prototype is an object which inherits properties from this.prototype,
e.g., Guru.prototype inherits from Person.prototype.
*/
wrapper.prototype = object(this.prototype);
return wrapper;
};

8 comments:

AaronNGray said...

p = proto;//exercise: why?

I believe this is due to how Javascript's closures work.

Me said...

I thought extending Object.prototype was a bad idea. Is that really needed?

Anonymous said...

This blog has moved to:

http://blog.higher-order.net

Please post commments and questions there.

This particular entry is located at:

http://blog.higher-order.net/2008/02/21/javascript-parasitic-inheritance-power-constructors-and-instanceof/

Anonymous said...

I'm not sure what the benefit of doing all that over this is:

function OriginalPerson() {
this.sayHello = function() {
alert("Hello, my name is " + this.getName());
};

this.getName = function() {
return 'Adam';
}
}

function Person(name) {
this.name = name;
this.getName = function() {return this.name;}
}
Person.prototype = new OriginalPerson;

function Guru(name, topic) {
this.name = name;
this.topic = topic;
this.getTopic = function() { return this.topic; };
}
Guru.prototype = new Person;

var mary = new Person("mary");
var someone = new Guru("someone", "something");
alert(mary instanceof Person);
alert(someone instanceof Person);
alert(someone instanceof Guru);

All of the instanceof checks return true.

I also think your object function may have a bug? Shouldn't F.prototype = new o; instead of just o?

krukow said...

This blog has moved to:

http://blog.higher-order.net

Please post commments and questions there.

This particular entry is located at:

http://blog.higher-order.net/2008/02/21/javascript-parasitic-inheritance-power-constructors-and-instanceof/

Rob Levin said...

--Anonymous--
The classical patter you show is great but you should realize that as soon as you define a reference type in the chain all instance will see changes so you'll essentially have statics. So if your OriginalPerson object had var arr = [1,2,3], you'd inherit that for all sub-instances and they'd "share state" so you'd have a static. You should use "constructor stealing" to prevent that - basically, the sub types call the super's constructor so that each instance has it's own state.

Wait, but that'll mean two calls to the parent constructor, one for the constructor stealing and one when you say child.prototype = new SuperType() So the next thing to do is create a helper function to essentially instantiatePrototypes(childConstructor, parentConstructor) that points the child's proto to the parent's proto, and then you only call the parent constructor once and don't get all the parent's instance variable "junk" on the sub type.

Impressed? Don't be, this is completely derivative as I'm paraphrasing Nicholas Zakas's book (end of Chapter 6). Zakas Professional Javascript... Chapter 6 and Crockford Good Parts Chapter 3 & 5 are wonderful to read together for grokking creation and inheritance in JS! I'm actually doing a TDD analysis of them in my "pamphlet" on testing javascript:
Testing Javascript

krukow said...

This blog has moved to:

http://blog.higher-order.net

Please post commments and questions there.

This particular entry is located at:

http://blog.higher-order.net/2008/02/21/javascript-parasitic-inheritance-power-constructors-and-instanceof/

Anonymous said...

The link given is invalid, it should be http://blog.higher-order.net/2008/02/21/javascript-parasitic-inheritance-power-constructors-and-instanceof.html