Prototype-based programming


Prototype-based programming is a style of object-oriented programming in which behaviour reuse is performed via a process of reusing existing objects that serve as prototypes. This model can also be known as prototypal, prototype-oriented, classless, or instance-based programming.
Prototype-based programming uses generalized objects, which can then be cloned and extended. Using fruit as an example, a "fruit" object would represent the properties and functionality of fruit in general. A "banana" object would be cloned from the "fruit" object and general properties specific to bananas would be appended. Each individual "banana" object would be cloned from the generic "banana" object. Compare to the class-based paradigm, where a "fruit" class would be extended by a "banana" class.
The first prototype-oriented programming language was Self, developed by David Ungar and in the mid-1980s to research topics in object-oriented language design. Since the late 1990s, the classless paradigm has grown increasingly popular. Some current prototype-oriented languages are JavaScript, Lua, Cecil, NewtonScript, Io, Ioke, MOO, REBOL and AHK.

Design and implementation

Prototypal inheritance in JavaScript is described by Douglas Crockford as:
Advocates of prototype-based programming argue that it encourages the programmer to focus on the behavior of some set of examples and only later worry about classifying these objects into archetypal objects that are later used in a fashion similar to classes. Many prototype-based systems encourage the alteration of prototypes during run-time, whereas only very few class-based object-oriented systems allow classes to be altered during the execution of a program.
Almost all prototype-based systems are based on interpreted and dynamically typed languages. Systems based on statically typed languages are technically feasible, however. The Omega language discussed in Prototype-Based Programming is an example of such a system, though according to Omega's website even Omega is not exclusively static, but rather its "compiler may choose to use static binding where this is possible and may improve the efficiency of a program."

Object construction

In prototype-based languages there are no explicit classes. Objects inherit directly from other objects through a prototype property. The prototype property is called prototype in Self and JavaScript, or proto in Io. There are two methods of constructing new objects: ex nihilo object creation or through cloning an existing object. The former is supported through some form of object literal, declarations where objects can be defined at runtime through special syntax such as and passed directly to a variable. While most systems support a variety of cloning, ex nihilo object creation is not as prominent.
In class-based languages, a new instance is constructed through a class's constructor function, a special function that reserves a block of memory for the object's members and returns a reference to that block. An optional set of constructor arguments can be passed to the function and are usually held in properties. The resulting instance will inherit all the methods and properties that were defined in the class, which acts as a kind of template from which similar typed objects can be constructed.
Systems that support ex nihilo object creation allow new objects to be created from scratch without cloning from an existing prototype. Such systems provide a special syntax for specifying the properties and behaviors of new objects without referencing existing objects. In many prototype languages there exists a root object, often called Object, which is set as the default prototype for all other objects created in run-time and which carries commonly needed methods such as a toString function to return a description of the object as a string. One useful aspect of ex nihilo object creation is to ensure that a new object's slot names do not have namespace conflicts with the top-level Object object.
Cloning refers to a process whereby a new object is constructed by copying the behavior of an existing object. The new object then carries all the qualities of the original. From this point on, the new object can be modified. In some systems the resulting child object maintains an explicit link to its prototype, and changes in the prototype cause corresponding changes to be apparent in its clone. Other systems, such as the Forth-like programming language Kevo, do not propagate change from the prototype in this fashion, and instead follow a more concatenative model where changes in cloned objects do not automatically propagate across descendants.

// Example of true prototypal inheritance style
// in JavaScript.
// object creation using the literal
// object notation.
var foo = ;
// Another object.
var bar = ;
// Object.setPrototypeOf is a method introduced in ECMAScript 2015.
// For the sake of simplicity, let us pretend
// that the following line works regardless of the
// engine used:
Object.setPrototypeOf; // foo is now the prototype of bar.
// If we try to access foo's properties from bar
// from now on, we'll succeed.
bar.one // Resolves to 1.
// The child object's properties are also accessible.
bar.three // Resolves to 3.
// Own properties shadow prototype properties
bar.two; // Resolves to "two"
bar.name; // unaffected, resolves to "foo"
foo.name; // Resolves to "foo"

This example in JS 1.8.5+

var foo = ;
// bar.prototype = foo
var bar = Object.create;
bar.three = 3;
bar.one; // 1
bar.two; // 2
bar.three; // 3

Delegation

In prototype-based languages that use delegation, the language runtime is capable of dispatching the correct method or finding the right piece of data simply by following a series of delegation pointers until a match is found. All that is required to establish this behavior-sharing between objects is the delegation pointer. Unlike the relationship between class and instance in class-based object-oriented languages, the relationship between the prototype and its offshoots does not require that the child object have a memory or structural similarity to the prototype beyond this link. As such, the child object can continue to be modified and amended over time without rearranging the structure of its associated prototype as in class-based systems. It is also important to note that not only data, but also methods can be added or changed. For this reason, some prototype-based languages refer to both data and methods as "slots" or "members".

Concatenation

In concatenative prototyping - the approach implemented by the Kevo programming language - there are no visible pointers or links to the original prototype from which an object is cloned. The prototype object is copied rather than linked to and there is no delegation. As a result, changes to the prototype will not be reflected in cloned objects.
The main conceptual difference under this arrangement is that changes made to a prototype object are not automatically propagated to clones. This may be seen as an advantage or disadvantage. It is also sometimes claimed that delegation-based prototyping has an additional disadvantage in that changes to a child object may affect the later operation of the parent. However, this problem is not inherent to the delegation-based model and does not exist in delegation-based languages such as JavaScript, which ensure that changes to a child object are always recorded in the child object itself and never in parents.
In simplistic implementations, concatenative prototyping will have faster member lookup than delegation-based prototyping, but will conversely use more memory. More sophisticated implementations can avoid these problems, however, although trade-offs between speed and memory are required. For example, systems with concatenative prototyping can use a copy-on-write implementation to allow for behind-the-scenes data sharing — and such an approach is indeed followed by Kevo. Conversely, systems with delegation-based prototyping can use caching to speed up data lookup.

Criticism

Advocates of class-based object models who criticize prototype-based systems often have concerns similar to the concerns that proponents of static type systems for programming languages have of dynamic type systems. Usually, such concerns involve: correctness, safety, predictability, efficiency and programmer unfamiliarity.
On the first three points, classes are often seen as analogous to types and are proposed to provide contractual guarantees to their instances, and to users of their instances, that they will behave in some given fashion.
Regarding efficiency, declaring classes simplifies many compiler optimizations that allow developing efficient method and instance-variable lookup. For the Self language, much development time was spent on developing, compiling, and interpreting techniques to improve the performance of prototype-based systems versus class-based systems.
A common criticism made against prototype-based languages is that the community of software developers is unfamiliar with them, despite the popularity and market permeation of JavaScript. This knowledge level of prototype-based systems seems to be increasing with the proliferation of JavaScript frameworks and the complex use of JavaScript as the Web matures. ECMAScript 6 introduced classes as syntactic sugar over JavaScript's existing prototype-based inheritance, providing an alternative way to create objects and deal with inheritance.

Languages supporting prototype-based programming