Wednesday, December 05, 2007

Prototyping and Dynamic languages

The nearest thing to heroin available at my local supermarket is Haagen Daz chocolate peanut butter ice cream. It's so good, I'm lucky I don't weigh 500 pounds.

Dynamic languages seem to be the flavor of the month in programming languages. And, why not? They enable fast development, concise code, and freedom from the rigid static type systems of Java or C#. Everyone, for example, seems to be raving about Ruby.

Javascript gets fewer raves and a lot of (sometimes well deserved) abuse. Leaving aside it's well documented faults, one nice thing about Javascript is that functions are truly first class citizens. Javascript's semantics regarding functions, complete with the ability to create closures, look cleaner to my eyes than Ruby's code blocks. Treating functions as data is a feature borrowed from languages like Scheme. From the more obscure Self language, Javascript borrows another advanced feature that - though elegant and useful - is often misunderstood: prototype based inheritance.

Prototyping is an idea worth understanding, because it will help you think beyond traditional object oriented programming. Prototyping and class-based inheritance can be seen at a higher level as two variants of the same general concepts. Simply put, prototyping is inheritance without classes.

Classes = delegation + interfaces

Like class-based inheritance, prototyping is a shorthand form of delegation. When one object delegates to another, the first object may simply pass a method call on to its delegate or may perform some additional functionality before or after the call. Those familiar with design patterns will recognize the Decorator pattern.

Not to get too far off track, but a lot of the power of aspect oriented programming boils down to a type of decorator usually called an interceptor in that context. The common ingredients here are delegation and a common interface.

To see that subclassing is a form of delegation, picture an instance of a Java class CodeMonkey, which is a subclass of Employee. Simple enough. Call a method on an instance of CodeMonkey and (conceptually) we first check if the method is defined in the class. If not, we delegate the handling of the call to the superclass Employee. Employee, in turn, can delegate to the class Object, which is the root of the inheritance hierarchy.

Subclassing defines an is-a relationship. A CodeMonkey is an Employee. In practical terms, this means that there is an implicit common interface implemented by both Employee and CodeMonkey, namely the interface of the superclass Employee. This is required for polymorphism. We have to be able to use a CodeMonkey anywhere we could have used an Employee.

There is something of a weird dichotomy between classes and objects. It doesn't seem weird because we're used to it, but think of all the awkwardness around reflection. That comes about because sometimes you want to treat a class as an object, while usually it's something quite different.

In Java, methods belong to the class, while instance variables belong to the object. Two instances can't have different implementations of a method like they can have different values for a member variable. Imagine, then, how all this might work in a language with no classes, only objects.

Prototypes, a shorthand for delegation

In Javascript, the only place for a method to live is on an object. This is natural because a function in Javascript is just another piece of data. Objects have members. Members are just data. They may be things like strings or integers or they may be functions. Since there are no classes there is no question of whether two objects can have different implementations of a method. Any two object, whether or not one is a prototype of the other, can share a method, or share a common method signature with different implementations. That's a lot of flexibility, right there.

In a such a language, an object can't delegate method calls to its class or superclass. So, an object can only delegate to another object. That's the essence of prototype based inheritance. An object's prototype is just another object. That object may have its own prototype, forming a chain, just as in classes. The root of the prototype chain is the prototype of Object. Well, that's true unless you change it, which brings up another strange aspect of prototypes. The prototype chain, being just data, can be modified at runtime. Thinking about that for a while could really warp your brain cells.

So how do prototypes mix with dynamic languages? Polymorphism can be taken for granted in a dynamic language. By virtue of “duck typing”, we don't need to worry about explicitly defining common base classes or interfaces. If the method walks like duck, we can call it. So, in most dynamic languages polymorphism is decoupled from type.

Prototyping decouples inheritance from type. In a dynamically-typed language, why expend effort to define type hierarchies? Dynamic languages are supposed to free us from worrying excessively about types. Certainly, delegation is still worth-while. Interfaces, though implicit, remain important. But what benefit do classes bring to the table?

Prototypes and dynamic languages

The popularity of class-based dynamic languages is due more to familiarity than function. Fluency in dynamic languages entails a shift in thinking of at least the same magnitude as going from procedural languages to OO. In a few years, building class hierarchies in dynamic languages may look as silly as the fortran-written-in-Java or C++ foibles that were so common not too long ago.

Prototyping fits much better than classes with the ethos of dynamic languages. It provides the benefits inheritance without the baggage of types. Like class based OO, prototypes are something you have to work with to get a feel for. Once you do, you'll appreciate its flexibility as a shorthand notation for delegation.

The gang-of-four advise us to favor composition over inheritance precisely because of the extra baggage carried along by subclassing. In the context of dynamic typing, prototype based inheritance offers a middle road, uncluttered by type hierarchies. Prototyping and dynamic languages - two great tastes that go great together!