Tuesday, March 30, 2010

Javascript for language geeks

A couple years back, I was surprised to discover some cool things about Javascript. I was really into Scheme at the time having read Structure and Interpretation of Computer Programs (aka SICP). It turns out, Javascript was strongly influenced by Scheme, and underneath a blasé curly-brace syntax was some cool stuff. Specifically, Javascript is a dynamically-typed object/functional scripting language as are Ruby and Python. But it's prototype-based object system is very different from either of those. Anyway, here are some notes from a little presentation I did, slightly edited and updated.

History

Javascript was created by Brendan Eich during the good old days of the browser wars. Netscape shipped LiveScript in Navigator 2.0 beta in September 1995 and re-released the language as JavaScript in March 1996. ECMA post-facto specification, ECMAScript standard: ECMA-262, followed. Douglas Crockford's The State and Future of Javascript tells the gory tale of the fall of the fourth edition and the rise of the fifth.

Edition 1June 1997
Edition 2June 1998
Edition 3December 1999
Edition 4...dragged on from 2000 through about 2008 and was finally abandoned
Edition 5December 2009

Closures

A closure is a package containing a function and some state. Here's an example. The purpose of an accumulator is to keep a running total, which requires state. Every now and then we can add something to the total. “Isn't a closure just a poor man's object?” you might well ask... or is it the other way around? Anyway, in Javascript, there's not much difference. Functions are objects and closures just tack on some state.

function createAccumulator(value) {
    return function(i) {
        return value += i;
    }
}
> acc = createAccumulator(100)
> acc(1)
101
> acc(1)
102
> acc(23)
125

Bag 'o properties

A Javascript object is just a bag of properties, like a hash. Since functions are a first class data type, some of those properties are functions. Properties obviously can change at run time. Want to add a method to an object? OK. Delete one? Fine. Change the behavior of a method? Just as easy. This is how objects in a dynamically typed language should be.

// creating a moose object
var moose = new Object();
moose.name = "Bullwinkle";
moose.species = "Alces alces";
moose.toString = function() {
 return this.name + ", " + this.species;
}
> print(moose);
Bullwinkle, Alces alces

> print("moose.name = " + moose.name);
moose.name = Bullwinkle

> print("moose[\"name\"] = " + moose["name"]);
moose["name"] = Bullwinkle

> m.greet = function(who) {  
  print("Hello, " + who + " my name is " + this.name + "!");
}

> m.greet("Chris")
Hello, Chris my name is Bullwinkle!

Constructors

Constructors are a little weird, but kinda cool. By convention, constructors are capitalized. Otherwise, they are just functions. The new keyword creates a new object which becomes this in the body of the fuction, which initializes the object. Because the constructor can build differently formatted objects in different circumstances, it's more like a factory than the poor hobbled Java constructor.

# moose constructor
function Moose(name) {
 this.name = name;
 this.species = "Alces alces";
 this.toString = function() {
  return this.name + ", " + this.species;
 }
}

> m = new Moose("Marty")
Marty, Alces alces    

Prototypes

The weirdest part of Javascript is its inheritance mechanism. Prototyping is inheritance without classes. We don't define templates for stamping out sets of similar objects. Instead, we pick one distinguished object and say, "All these objects are like that one". In other words, we delegate to our prototype object anything we don't need to handle in a customized way.

The new spec says it this way:

In a class-based object-oriented language, in general, state is carried by instances, methods are carried by classes, and inheritance is only of structure and behaviour. In ECMAScript, the state and methods are carried by objects, and structure, behaviour, and state are all inherited.

What's a little weird is how Javascript implements the idea syntactically. Have a look at the example below. Let's factor out stuff common to all moose.

moose_proto = new Object();
moose_proto.species = "Alces alces"
moose_proto.toString = function() {
 return this.name + ", " + this.species;
}

function Moose(name) {
    this.name = name;
}
Moose.prototype = moose_proto;

> a = new Moose("Bullwinkle");
> print(a);
Bullwinkle, Alces alces

To me, it's strange that x.prototype = y doesn't mean "The prototype of x is y." It means, "If x is a constructor, the objects created by x will have y as their prototype." Ya gotta admit, that's nutty. Also nutty is trying to implement something like the Java keyword super. Look around and you'll find a number of long and involved ways to get that behavior to work correctly.

One of key aspects of inheritance in Java is polymorphism. But, in duck-typed dynamic languages, every method call is polymorphic already. No class hierarchy needed. And why bother mucking about with user-defined type hierarchies anyway? Aren't dynamic languages supposed to free us from diddling about with that sort of thing?

I'm glad classes didn't make it into Javascript 5. But, I wouldn't be surprised if they come eventually, for a couple of reasons. Some people think prototypes are too weird and scary for the average Joe Coder. I don't, but I do think Javascript obfuscates the issue a bit. ActionScript has them. Prototype has them. I suppose that means there's a demand, so they'll probably come.

Links

No comments:

Post a Comment