Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Friday, May 1, 2015

Class-free object-oriented programming in Javascript

Douglas Crockford has given another talk on the future of Javascript, with particular attention to what he now considers the Right Way to do object-oriented programming, which is quite a departure from classical thought. Years ago, he stopped using new explicitly, and came up with Object.create to use instead, for more straightforward use of Javascript's prototypal inheritance design.

Now, he has also stopped using this, which means he has effectively abandoned Javascript's notion of inheritance altogether. He calls his new style "class-free object-oriented programming", the basic feature of which is that every object actually contains its own methods, rather than sharing them in a prototype or parent object. This is "inefficient" by some measure, but if you think about it, the sharing of methods is simply an optimization, and in most cases, optimizing for space in JS is not going to make a critical difference.

It happens that this was the style I adopted when I got into programming JS, because I was not trained up in classical object-oriented languages. Ironically, more recently, I did get into the typical use of new and prototypes, but I'm going to reconsider that for a while.

Crockford's basic boilerplate for a constructor is:
function constructor(spec) {
  let {member} = spec,
      {other}  = other_constructor(spec),
      method   = function () {
        // accesses member, other, method, spec
      };

  return Object.freeze({
      method,
      other,
  });
}
Which is to say that there is no built-in inheritance; objects that are composed-in are simply member sub-objects. You can, of course, make pass-through member calls.

Danny Fritz blogged about the concept of class-free OO programming, with some helpful illustrative examples, but his techniques differ somewhat from Crockford's, notably in the use of this. Predictably, I have a take that is somewhat different from each of them, but which I think marries their best features.

Crockford uses constructors for everything, which has the code smell of boilerplate in the form of the return Object.freeze portion. Fritz has constructors and extenders, with a copy-in extend function. I have always hated such extenders, and I don't see a reason to have a distinction between constructors and extenders.

Instead, I propose that the proper separation of duties is to have a universal constructor and everything else is an initializer. This gives you freedom to inherit and allows you to avoid the security hazards of this.

The universal constructor looks like this:
Function.prototype.construct = function (spec) {
    var obj = {};
    this(obj, spec);
    return Object.freeze(obj);
}     
Almost too simple to bother with, but it takes care of the boilerplate issue. Note: I formerly had defined this as Object.construct, but this definition makes every function be a potential constructor, which seemed more appropriate.

Now, for any object type (or extension type), all you have to concern yourself with is the initialization. I will illustrate using Fritz's examples of alligator, duck, and goat objects that are defined in terms of extensions Animal, Walking, Swimming, Flying. This is a straightforward port of his alligator:
function alligator (self, spec) {
    self.name = 'alligator';
    self.word = 'grrr';
    animal(self);
    walking(self);
    swimming(self);
}
All of the extensions are just initializers called on the self object (which, being passed in, avoids the security problems of this), and the alligator function is, itself, just an initializer. spec is not used in this case, but I have kept it to show how Crockford's model fits. Most objects do have inputs to their initialization. Similarly, animal et. al. would normally take a second argument.

To create an instance:
var myAlligator = alligator.construct(spec);
It should be clear from this how duck and goat would similarly be created, but let's take a look at the extensions. Again, a straightforward port of animal:
function animal(self) {
    self.name = 'name';
    self.word = 'word';
    self.talk = function () {
        console.log(self.name + ' says ' + self.word);
    }
};
It becomes clear at this point that all the things that inherit from animal have a name and a word. Initialization of those should be done with a spec, rather than having placeholder defaults and each derived object having to know about them:
function animal(self, spec) {
    self.name = spec.name;
    self.word = spec.word;
    self.talk = function () {
        console.log(self.name + ' says ' + self.word);
    }
};
Now we can rewrite alligator:
function alligator (self, spec) {
    animal(self, spec);
    walking(self);
    swimming(self);
}
alligator.construct({name: 'alligator', word: 'grrr'});
So our alligator doesn't merely contain animal, walking, and swimming objects (as would be the case in the Crockford model), but it has been initialized to have those traits directly. This requires that all the traits play nicely with each other. If that can't be guaranteed, Crockford's model offers more security, and can obviously be implemented with pass-through methods like so:
function alligator (self, spec) {
    animal(self, spec);
    var walkTrait = walking.construct();    var swimTrait = swimming.construct();    self.walk = walkTrait.walk;
    self.swim = swimTrait.swim;
}
alligator.construct({name: 'alligator', word: 'grrr'});
Note that I have initialized the alligator self as an animal, then added the other features as traits. Whether you initialize on the object itself or create sub-objects is up to you.

Friday, July 2, 2010

Javascript and Dojo

Since I don't have any theater projects coming up, I figured I might as well fill in the space with what I'm doing at work. My latest gig is working with Javascript and (in particular) the Dojo toolkit to develop some web apps.

In the process of coming up to speed on Javascript and Dojo (neither of which I have used a great deal), I'm hitting Google a lot to learn how the various things work. There is, of course, some level of documentation on everything at the Dojo site, but in some cases it's incomplete, and I have to look elsewhere for someone to explain it better. So in the hopes that maybe someone else will be doing the same sort of thing someday, I'm offering this post to explain how dojo.deferreds work.
Update: Naturally, an updated version of Dojo has made this whole discussion nearly obsolete. The new features in the deferred are more intuitive.

What is a dojo.deferred?

In short, a dojo.deferred is an alternative to threads, a way of having relatively independent parts of your program running at that same time. As the documentation page says, "threads are hard."

Think of it as a chain of functions, which you build up one link at a time. You schedule them in the deferred, and kick it off, and it will work its way through them while the rest of your program continues to run uninterrupted (particularly useful if it's a time-consuming operating like waiting for a response from some remote server). If it reaches the end of its chain, it will sit and wait until you put another link on it.

Each link in the chain is a pair of functions. Which of them is called depends on what the previous link returned: if it returned an error, the errback function of will be called; if it returned a normal value, the callback function will be called. So it is critical that each function return something.

Building the chain

There are four possible functions you can call to add a link to the chain: addCallback, addErrback, addBoth, and addCallbacks, and which you call depends on whether you want to specify just a callback, just an errback, the same function to be run in either case, or separate functions for each. In general, the first link in the chain can be just a callback, because there's no danger of an error preceding it. But subsequent links should include both; if a piece unexepectedly ends in error and you have no errback to handle it, the whole deferred silently grinds to a halt.

Kicking it off

It might not be obvious that you have to actually start the chain processing. It doesn't start chugging immediately, you have to "prime the pump" with something for it to consider as the return value of the previous link. You do this by calling its callback() method, which you pass whatever value you want sent to your first callback.

Example time

I find that most examples are too detailed, so I'm going to keep this one more abstract, so that you see what the deferred is all about and don't get lost in the weeds.


//Let's say we have some functions we're going to chain up
/*
* The deferred is going to call each with the
* result returned by the previous one
*/

function thing_one(i) {
return 'Done with 1';
}
function thing_two(result) {
return 'Done with 2';
}
function thing_three(result) {
return 'Done with 3';
}

/* And here's a generic error function to
* use for every step
*/
function myErrback(e) {
alert("Some step of the deferred returned an error!");
}

// Create the deferred
my d = new dojo.Deferred();
// Chain up the steps
// No chance of failure, so no errback on the first
d.addCallback(thing_one);
d.addCallbacks(thing_two, myErrback);
d.addCallbacks(thing_three, myErrback);
// Kick it off by passing 'Go!' to thing_one
d.callback('Go!');

Note that even after you kick it off, you can add more function pairs to the chain, and they will be processed in the order received. I hope this helps somebody understand this nifty tool.