Learning From Prototypes

Advantages of prototypes

Classes are great conceptually, in that they are easy to understand and work with. Prototypes (PrototypeBasedProgramming) are not quite as easy to work with, but don't have some of the limitations of classes. Namely, it's painful to create one-instance-only objects (i.e. SingletonPatterns) with classes, but it is quite natural to do this with prototypes.

Prototypes also let you modify objects' behavior at run-time, or create one-off instances. This is great for things like event handlers, where it's a lot of work to create a custom widget if you have to give it its own class. Newer languages offer anonymous classes and closures, but it's still awkward. Anonymous classes (Java) still have a lot of extra syntax if you just want to override one method, and delegates (C#) are better but still unwieldy.

The way I see it, there are two big advantages of PrototypeBasedProgramming.* First, you can modify the behavior of many objects at once by modifying their prototype. For instance, you can add a method to a prototype and all the objects created from that prototype automatically gain that method. Second, you can modify the behavior of individual objects without the need for a one-off class.

I like the power of prototypes, but am more comfortable with the concept of classes. I'm not sure that prototypes-as-a-concept offer any real advantage over classes. Rather, I think the problem is the limitations built into class-based languages. If we were to remove these limitations, would prototypes still have anything to offer?

Drawbacks of prototypes

My main beef with prototypes is that necessity of some prototypical object to exist; you don't use that object, you just clone it to create new objects, the objects that you actually use. But then it seems like a waste to have all these prototypes sitting around not doing anything.

Consider a database object. With classes, you instantiate the database class to create a new database object, which connects to the database and lets you send queries. With prototypes, you have this prototypical database object already in existence. What does a "prototypical" database object look like? Is it connected to a "prototypical database"? Probably not. So can you actually do anything with it? If not, why should we pretend that this prototype is just as good as a real live instantiated-and-connected database object?

In the overview of the SelfLanguage (http://research.sun.com/self/papers/self-power.html), the authors compared class instantiation to building a house from a blueprint, and prototype-cloning to just copying an existing house. Yes, I suppose copying is a simpler metaphor, but it doesn't make sense as a language-design metaphor. Copying requires that a source object already exist; if we were building a house, it would be an awfully inefficient way for an architect to design the house by building a full-scale model and then saying to the construction company, "Here, copy that." Classes also seem more natural when class instantiation requires one or more parameters.

Beefing up classes

Instead of throwing out the baby with the bathwater by ditching classes in favor of prototypes, why don't we give classes the strengths of prototypes? Above I listed two of them.

JavaScript 2.0 has a beautiful, elegant system for allowing extensions to prototypes while limiting the scope of such changes so that modules not expecting such changes are unaffected. (See http://www.mozilla.org/js/language/js20/core/namespaces.html.) In essence, if you modify a prototype by adding or modifying methods or attributes, your modifications are only visible within a limited scope, such as the enclosing package.

I don't find this elegant. Objects that behave differently depending on the namespace of the code that is accessing them? That just sounds like a recipe for confusion to me. -- DavidSarahHopwood

Given such a system, it should be easy to add the same abilities to a class-based language. One could, for example, add a new method getSize to the java.lang.Object class in JavaLanguage. With a sophisticated namespacing system, the method would only be visible in a limited scope, and so wouldn't interfere with other getSize methods in different classes.

This can be achieved much more simply than by allowing objects to behave differently when called from different namespaces. Allow the name of a method to be an unforgeable nonce object rather than a string (OzLanguage allows this, for example). Then the nonce is a capability (in the sense of ObjectCapabilityModel) to call that method; its availability can be restricted using LexicalScoping, and it can be referred to by any local name. -- dh

I don't see why one couldn't do something like

 string message = "Hello world!";
 message.rot13  = function() { ... };

message.rot13();
in a class-based language. We could mimic prototypes by searching for a method in the object upon which the method is invoked, and then the object's class if the object does not override the method.

If you're going to simulate prototypes, why not call them prototypes? It is simpler to define classes in terms of prototypes than vice-versa. -- dh

Consequences of these changes

If objects could have methods added at run-time, this would impact StaticTyping. In the code example above, the compiler could not declare the message.rot13() statement invalid just by looking at the string class.

The best way to type a language with dynamic code modification features is probably to use SoftTyping. Use of these features will not be statically typesafe, but in a soft-typed language that will just cause a run-time error. However this presents difficulties for efficient implementation, because optimisations may be invalidated by a code change. It is much easier to implement such a language efficiently if you can't change the methods of all objects -- only objects that are declared in a particular way. We can then get most of the flexibility by declaring objects that act as factories this way, leaving objects that are used more often static. -- dh

To my mind, having these abilities would give classes the power of prototypes, but without the drawback. You could modify objects at will even after instantiation, like with prototypes, but would have the conceptually cleaner metaphor of instantiation rather than cloning.

-- JohnKugelman

First of all, only you consider not having to instantiate an object, or keeping a prototype around, a drawback. Geez, applying that logic, why don't we get rid of classes as objects? Hell, why don't we just revert back to C++? We wouldn't want to keep any "extra" objects around, would we? We wouldn't want to be "inefficient", would we?

Second, you don't seem to understand what prototypes are about. The power of prototypes comes from a dynamic delegation system. Instead of limiting delegation to a static inheritance hierarchy, an object can choose to delegate to anything under the sun. This means that you can create wrappers very easily and that those wrappers will be extremely powerful. Not only that, but you can do so as a matter of routine instead of screwing with an extremely dangerous method like DoesNotUnderstand:.

My concern is not space efficiency. It is that objects exist which cannot be used. I prefer that it be explicit that resource objects, houses, etc. must be constructed before being useful. With a class, you know you have to instantiate the class before you can use it. With a prototype, it's not explicit that you have to clone the prototype and initialize it (open this file, connect to this database) before you can use it.

And there's your problem. You think that a language should have a hard-coded syntax that's specially tailored to your personal prejudices.


(*) SelfLanguage, the prototypical prototype-based language, claims other advantages, but I don't see them as such. Self rightly did away with the distinction between variables and methods, but C#'s use of properties shows that class-based languages can do that, too. The creators of Self also write that it "allows for the blurring of the differences between objects, procedures, and closures. Reducing the number of basic concepts in a language can make the language easier to explain, understand, and use. However, there is a tension between making the language simpler and making the organization of a system manifest. As the variety of constructs decreases, so does the variety of linguistic clues to a system's structure."

Right on.

Except for the fact that the distinction between an object and a method is manifest in Self ... precisely when you care about it. And it is invisible in Self ... precisely when you don't care about it. The idea that you can make a universal judgement that "more syntax is good" or "more syntax is bad" is absurd since it's a subjective criterion that depends on the user's frame of mind. When you're looking at an object from the outside, you don't care whether a slot is a method, data or an object. It's only from inside the slot that you care, and then it is obvious.

You can easily add syntax to a language that has little but you can't take away syntax from a language that's got too much. So it follows that languages should have as little syntax as possible. However, even that's not optimal since the best thing would be to directly manipulate parse trees and leave all of the syntactic representation to the IDE.


For what it's worth, I've been working with Self for the past eight months, and I'm surprised to hear you say that prototypes aren't as easy to understand as classes. It's hard for me to know which one I find easier to understand, because I was already familiar with languages like Java and Smalltalk and Ruby before I learned Self. But I find prototypes very natural, and I find that beginning programmers seem to have a lot of trouble understanding classes - especially understanding the difference between a class and an object. ("No, no, you wrote Point.x(); you need to call x() on an instance of Point, not the Point class itself...")

I certainly don't have a lot of experience with this, though. Mostly just listening to the people around me as I went through undergraduate ComputerScience courses. And occasionally trying to teach Java programmers how Smalltalk works. ("Everything is an object, and every object is an instance of a class. A class is itself an object; it's an instance of a metaclass. Don't worry about what a metaclass is; the point is that you can have instance-side methods and class-side methods. The class-side inheritance hierarchy mirrors the instance-side inheritance hierarchy. To make a new object, you just get the class object and send #new to it.")

Prototypes just seem so much simpler. "Everything is an object. To make a new object, you send #copy to an existing object."

There's also a whole psychological argument here. Right now I'm partway through WomenFireAndDangerousThings, which has some interesting ideas related to the classes vs prototypes issue.

--AdamSpitz

You're right, I was sloppy with my wording. Prototypes are definitely easier to understand if you're coming to object-orientation in general with a fresh mind. I've definitely seen novices struggle with the difference between instance and class variables/methods. It's a less-than-obvious distinction.

I meant to say that prototypes don't make as much conceptual sense as classes do, at least to me. The prototype metaphor is extremely simple--too simple for me. SelfLanguage aims to do away with a lot of distinctions made in traditional OO languages, ones I think are quite natural and helpful.

I confess that I haven't done any Self programming, but I've done quite a bit of JavaScript work. I find that even though JavaScript is prototype-based and not class-based, almost all of the object-oriented code out there mimics classes. I initially tried doing things in a very prototype-y way, by cloning objects, but found it much easier going once I switched to using constructors.

Historically speaking, classes were a natural evolution of the procedural approach of calling initialization and shutdown procedures for modules, and create/destroy methods for complicated data structures. From that point of view, I consider classes with constructors and destructors more "natural" than prototypes, though more complex.

As for instance/class methods and the like, I agree that this can be very confusing. To my mind this confusion has more to do with languages like Java and C# aiming to be "pure" OO, which means a lot of things get shunted into classes that shouldn't be there. C++, despite its horrid syntax, is better in aiming to be multi-paradigmatic, allowing non-class functions and variables.

-- JohnKugelman

This more than anything so far proves to me that you don't know what the hell you're talking about. Java aiming to be pure-OO? Give me an f-ing break!

Whether or not Java is pure OO (IsJavaObjectOriented) is an interesting debate. Certainly, Sun and others like to claim that it is so.

There's nothing to debate. Java isn't pure OO and that's all there is to it. Smalltalk is only barely sufficiently OO to qualify as pure OO.

[Okay, I'll bite. What do you consider to be the purest of sufficiently OO languages?]

{{ Act1 was close. -- DavidSarahHopwood }}

There isn't one. And Smalltalk is admitted to the club precisely because its exclusion would leave it empty.


The database example is kind of a weird one. Most of the time, we really do try to have the prototypes be real, complete, functioning objects. If I remember the story correctly, DavidUngar was actually reluctant to make data slots in Self default to nil; he wanted to force people to explicitly choose an initial value, precisely because he didn't want to encourage the creation of non-functioning prototypes. (I'm not sure exactly why he relented, actually.)

The only reason why it's awkward (in the real world) to construct a house by copying an existing house is because houses are big, and so it seems like wasted effort to make a whole big thing that you're not going to use. For smaller things in the real world, it's often way more natural to copy an example (and then change what you need to) than to follow instructions for creating a whole new thing.

The computer world can be better than the real world in this way. In the computer world, creating an actual house object isn't any more difficult than creating instructions to create a house object. So why not? Sometimes it makes sense to make the computer world imitate the real world as closely as possible, but sometimes we can just do better. And it's really nice to have this real, working prototype sitting there for you to poke and prod and play with.

-- AdamSpitz

Interesting story. I'm glad to hear that having non-functioning prototypes is indeed considered bad practice.

Can you give me an example of where it would be useful to have this sort of unrequested object sitting around? I can think of plenty of examples where it would be either expensive or wouldn't make sense to have an object sitting around unrequested--many resource objects, like images or widgets or windows...

...Or, relevant to my work, a parser object in a compiler, which spits out parse trees when you feed it source code files. Creating the parser object is rather expensive; and what language would a prototype parser parse, anyways? If you don't give it a language to parse, it wouldn't be terribly useful. And if you did give it some language, building the parsing tables would be a really costly operation, something you wouldn't want to do if your goal were to clone that prototype and then create your real parser.

-- JohnKugelman

Hrmm? But if you have a working parser, why would you need to clone it? Just use the one that works.

I suggest you take a look at the Vortex source code: http://www.cs.washington.edu/research/projects/cecil/www/Release/index.html. It's an aggresively-optimizing compiler for CecilLanguage, Java, SmallTalk, and C++, written in Cecil. Cecil is a next-generation PrototypeBasedLanguage: it's Self plus MultiMethods and PredicateClasses (technically prototype objects, but the Cecil group isn't fussy). Look in Cecil/src/compiler/front-end/.

The compiler's set up so that cecil_parser inherits from vortex_parser inherits from parser. Each object adds a bit and overrides a bit. The method scanner.new_cecil_parser copies cecil_parser, sets the scanner, and returns the parser.

If you're worried about efficiency, most prototype-based languages use an implementation idea called a "map" to factor out commonalities between objects. Not every part of an object actually gets copied when you clone it: it's not like the runtime makes copies of the code segments of all the methods, for example. It's like CopyOnWrite for objects. Adam could probably tell you more about it.

-- JonathanTang

Well, for instance my compiler is set up so that the scanner and parser are generic. When you create a parser object you give it a language's grammar and it generates the parsing tables necessary to parse that grammar. So in theory I could create multiple parsers for several different languages. (For example, if I were using this in a web browser I might have an HTML parser, a CSS parser, and a JavaScript parser.) Given such a setup, I wonder what the prototype parser might be doing.


Prototypes are not more wasteful than classes; they are less so, because they are more powerful. The primary complaint I see above is that a useless prototype is required in order to instantiate new objects. But this is not true for two reasons: first, a prototype does not need to contain anything more than a class does; the constructor function can add new slots to the created objects, so that the "prototype" contains only the methods and class data, not the instance data; this makes it exactly equivalent to a class. Second, there's no requirement that the prototype object be useless; it can be in active use when its descendant is created.

Others have cited static typing. See http://javalab.cs.uni-bonn.de/research/darwin/project.html for an example system, which is good reading for anyone interested in prototypes (it explains very well the most important concepts of prototypes, delegation).


Are prototypes more expressive then classes?

Of course they are...or are they?

It is true that you can implement any class-hirarchy relationship using prototypes, provided you can delegate to more than one object. (I assume that some prototype-based languages have MultipleInheritance.) Additionally, the idea of just being able to create an object can be used for many things. For example, namespaces. It is much more ellegent, IMHO, to declare an object with the utility methods you need, rather than either (a) declaring a class that doesn't actually make anything (as in Java) or (b) declaring a class with the methods, make its constructor private, copy and paste a bunch of boilerplate for lazy instantiation, and then have to call getInstance() every time you use it. That's really the problem with any object or object-like entity that you only need one of, when you have classes.

However, prototypes do make you lose a bit of expressiveness in interfaces. Static typing requires algorithms which are a lot more complex. Ensuring that something implements an interface is harder, as it could implement it one day and not implement it the next. Fragile base class becomes fragile base object, and becomes even more of a problem, because if people can modify runtime behavior all willy-nilly, there isn't really much point to private variables. Guaranteed invariants? Forget about it. Pattern matching doesn't quite work without constructors, although I suppose it could be generalized.

Speaking of constructors, constructors don't mesh well with prototypes. They are, after all, a method which is almost guaranteed to never be needed on a fully instantiated object, but prototypes keep them on all the same. Unparameterized ones like clone() are OK to inherit, but, quite often, the construction of a subtype doesn't even make sense when you pass in the parameters meant for a supertype. For example, if you delegate String from Array, then Liskov substitution stops working once you replace Array cloneWith(Object... elems) with String cloneWith(Character... chars). If we just left those methods in their own factory objects, everyone would be happier.

Now, of course, there's no real merit to my complaints if prototypes are used in a dynamically typed system, which they usually are. However, I'd say that you can implement most of the advantages of prototypes in statically typed systems.

1. Singletons with good syntax.

     // Scala.
     object Singleton {
          // ...
     }
Check.

2. Delegation

    # Perl 6
    class Dog {
       # ...
       has Fur $.fur handles <groom addFleas getMuddy>
    }
Not in any statically typed class language, but it can happen.

3. Runtime modification of...

    # classes/prototypes:
    use MONKEY_TYPING;
    agument class C {
        # do something
    }

# instances (ruby) def aVariable.aNewMethod(args) # do stuff end
And, of course, you can also use multimethods.


See Also: ClassesPrototypesComparison. PrototypeDrivenDevelopment


EditText of this page (last edited February 25, 2011) or FindPage with title or text search