A Comparison of Class-Based and Prototype-Based Languages
Edward Byne
Friday 24 April 2015
Outline
- Historical and object-oriented context
- Overview of classes
- Overview of prototypes
- Key differences
1. Context
Timeline
- Until 1950s: assembly language
- 1950s and 1960s: emergence of high-level languages e.g. FORTRAN, ALGOL (procedural languages)
- 1967: Simula - considered the first object-oriented programming language
Object-oriented languages
"Object-oriented design requires that you shift from thinking of the world as a collection of predefined procedures to modeling the world as a series of messages that pass between objects."
Sandi Metz, Practical Object-Oriented Design in Ruby (2013)
- Data (state) and operations (behaviour) combined into a single thing
- Objects invoke one another's behaviour by sending each other messages
- Encapsulation - distinction between internal complexity and external interface
Object-oriented languages
"In a world of objects, new arrangements of behaviour emerge naturally. You don't have to explicitly write code for the spouse_steps_on_cat procedure, all you need is a spouse object that takes steps and a cat that does not like being stepped on. Put these objects into a room together and unanticipated combinations of behaviour will appear."
Sandi Metz, Practical Object-Oriented Design in Ruby (2013)
- Well designed objects can be reused in different and changing contexts
- OO languages have unlimited object types
- Objects can be combined using inheritance and composition techniques
Object-oriented languages
- Need a mechanism for creating objects
- That's where classes and prototypes come in
Timeline
- Until 1950s: assembly language
- 1950s and 1960s: emergence of high-level languages e.g. FORTRAN, ALGOL (procedural languages)
- 1967: Simula - considered the first object-oriented programming language
- Simula introduced the concept of classes
- Classes used in many subsequent OO languages e.g. Smalltalk (1980), Java (1995), Ruby (1995)
- 1987: Self - first prototype-based language
- 1995: JavaScript - the only widely-used prototypal language
Java
JavaScript
Self
Scheme
Origins of JavaScript
Syntax
Functions
Prototypes
Largely an amalgamation of three different languages:
2. Classes
Classes
- A class provides a blueprint for the construction of similar objects
- Defines methods (behaviour) and attributes (data)
- Every object must be an instance of a class
- Built in classes e.g. String
greeting = String.new("Hello, World!")
greeting = "Hello, World!"
OR:
greeting.length
# - > 13
greeting.upcase
# - > "HELLO, WORLD!"
Creating Classes
We can create our own classes:
class Dog
attr_reader :name
def initialize(name)
@name = name
@tired = false
end
def bark
"Woof!"
end
def walk
@tired = true
end
def sleep
@tired = false
end
def tired?
@tired
end
end
dog = Dog.new("Henry")
dog.bark
# - > "Woof!"
dog.tired?
# - > false
dog.walk
dog.tired?
# - > true
Classes - Inheritance
A class can be derived from another class:
class Beagle < Dog
def howl
"Awoooooo!"
end
end
beagle = Beagle.new("Henry")
beagle.bark
# - > "Woof!"
beagle.howl
# - > "Awoooooo!"
Beagle.superclass
# - > "Dog"
Classes - Composition
A class can contain other classes:
class Tail
def wag
"Wag wag!"
end
end
class Dog
attr_reader :name
def initialize(name)
@name = name
@tired = false
@tail = Tail.new
end
def wag_tail
@tail.wag
end
end
dog = Dog.new("Henry")
dog.wag_tail
# - > "Wag wag!"
3. Prototypes
- JavaScript is classless - an object can be easily created using an object literal ({})
- No need to specify a class or blueprint
var dog = {
name: "Henry",
isTired: false,
walk: function() {
this.isTired = true;
},
sleep: function() {
this.isTired = false;
}
};
JavaScript - creating objects
dog.name;
// - > "Henry"
dog.isTired;
// - > false
dog.walk();
dog.isTired;
// - > true
Prototypal inheritance
- We can create an empty object...
var empty = {};
empty.toString;
// - > function toString() { ... }
Object.getPrototypeOf(empty);
// -> Object {}
- But it has hidden properties...
- Supplied by its prototype...
"The problem with object-oriented languages is they've all got this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle."
Joe Armstrong, interviewed in Coders at Work
- Instead of being instances of classes, objects inherit directly from other objects
- Every object has a hidden link to an object (prototype) from which it can inherit properties
Prototypal inheritance
Prototypes - constructors
- JavaScript provides the constructor invocation pattern
- First we create a function:
var Dog = function(name) {
this.name = name;
};
dog = new Dog("Henry");
// - > { name: "Henry" }
- And when a function is invoked with the new prefix a new object is created using this function...
- Functions automatically get a property named prototype, which by default holds an empty object deriving from Object.prototype. This becomes the prototype of the new object:
Dog.prototype;
// - > {}
Prototypes - constructors
- We can add properties to the constructor's prototype, so that those properties are available to any instances of that constructor:
var Dog = function(name) {
this.name = name;
this.isTired = false;
};
Dog.prototype.bark = function() {
return "Woof!";
};
Dog.prototype.walk = function() {
this.isTired = true;
};
Dog.prototype.sleep = function() {
this.isTired = false;
};
dog = new Dog("Henry");
dog.name;
// - > "Henry"
dog.bark();
// - > "Woof!"
Prototypes - inheritance
Prototypes can inherit from other prototypes:
var Beagle = function(name) {
this.name = name;
};
Beagle.prototype = new Dog();
beagle = new Beagle("Henry");
beagle.bark();
// - > "Woof!"
var Tail = function() {};
Tail.prototype.wag = function() {
return "Wag wag!"
};
var Dog = function(name) {
this.name = name;
this.tail = new Tail();
};
Dog.prototype.wagTail = function() {
return this.tail.wag();
};
dog = new Dog("Henry");
dog.wagTail();
// - > "Wag wag!"
And composition is also possible:
Prototype chain
- Differential inheritance - JavaScript will look from the bottom of the prototype chain upwards until it finds the referenced property
- If not found then undefined is returned
Object.prototype
Function.prototype
Array.prototype
Dog.prototype
Beagle.prototype
4. Key differences
Key Differences
1. Inheritance is optional in prototypal languages
- Classical - every object must be an instance of a class
- Prototypal - an object can inherit from other objects, but it doesn't have to:
var dog = Object.create(null);
Object.getPrototypeOf(dog);
// - > null
Key Differences
2. Prototypes may help to avoid complex taxonomies
- Classical - a class can only inherit from its parent
- This may lead to complex hierarchies/taxonomies
- Prototypal - an object can inherit from any other object
- Prototypal languages aren't interested in where an object comes from, just what it can do
Key Differences
2. Prototypes may help to avoid complex taxonomies (continued)
- Douglas Crockford advises against using the constructor invocation pattern
"JavaScript is conflicted about its prototypal nature."
"The pseudoclassical form can provide comfort to programmers who are unfamiliar with JavaScript, but it also hides the true nature of the language. The classically inspired notation can induce programmers to compose hierarchies that are unnecessarily deep and complicated."
Douglas Crockford, JavaScript: The Good Parts, 2008
Key Differences
3. Prototypal objects inherit state as well as behaviour
- Classical - each instance of a class has its own state (instance variables)
- Prototypal - an object inherits state from its prototype which can then be modified
Key Differences
4. Prototype changes have retroactive heredity
- Classical - an object is limited to the methods and instance variables prescribed by its class (and any parent classes) at the time it is instantiated
- Prototypal - changes to an object's prototype will also affect the state/behaviour of the object, even after creation
dog = new Dog("Henry");
dog.bark();
// - > "Woof!"
Dog.prototype.bark = function() {
return "Wau!";
};
dog.bark();
// - > "Wau!"
Key Differences
5. Prototypal objects are dynamic
- Prototypal - the object itself can also be modified at any time
dog = new Dog("Henry");
dog.bark();
// - > "Woof!"
dog.bark = function() {
return "Wau!";
};
dog.bark();
// - > "Wau!"
dog.isHungry = true;
dog.isHungry
// - > true
Key Differences
6. Prototypal objects do not generally have private data
- Classical - an object has control over its data and can have private methods
- Prototypal - object properties are generally public
- This is one of the main reasons that Douglas Crockford advocates a functional programming style over the "pseudoclassical style"
Functional vs pseudoclassical
- A review of functional programming goes beyond the scope of this talk but here is an example:
- The function's variables are not accessible outside the function but will always be accessible to inner functions
var dog = function(name) {
var name = name;
return {
bark: function() {
return "Woof!";
},
readNameTag: function() {
return name;
}
};
}
henry = dog("Henry")
henry.name;
// - > undefined
henry.readNameTag();
// - > "Henry"
Functional vs pseudoclassical
- But with ECMAScript 6 it seems that constructors are here to stay:
class Dog {
constructor(name) {
this.name = name;
this.isTired = false;
}
bark() {
return "Woof!";
}
walk() {
this.isTired = true;
}
sleep() {
this.isTired = false;
}
}
var Dog = function(name) {
this.name = name;
this.isTired = false;
};
Dog.prototype.bark = function() {
return "Woof!";
};
Dog.prototype.walk = function() {
this.isTired = true;
};
Dog.prototype.sleep = function() {
this.isTired = false;
};
Questions?
Classical and Prototypal Languages
By Edward Byne
Classical and Prototypal Languages
- 255