214 Prototype-Based Inheritance

Description

Ay├║ Rubyists,

This week’s quiz is to implement prototype-based Inheritance. In prototype-based inheritance objects gain their properties from other objects, not from class hierarchies. Here’s a code example to illustrate setting a prototype explicitly:

pele = {:name => "Pele", :sport => "football", :position => "Forward"}
pele_jr = {:name => "Pele Jr."}
pele_jr.prototype = pele
pele_jr.name # => "Pele Jr."
pele_jr.sport # => "football"
pele_jr.position # => "forward"

You may notice that this doesn’t work: NoMethodError: undefined method 'prototype' for {:name => "Pele", :sport => "football", :position => "Forward"}:Hash. If it did work then there’s not much for you to do on this quiz!

Hopefully this example illustrates some of the basics of how prototype-based inheritance works: there is one prototypical football player and other players defer to that player if they are queried for an attribute that hasn’t been specified. This relates closely with our intuitions and how we discuss things “it’s kind of like Starbucks, except the drinks are bigger”

new_coffee_shop.prototype = starbucks
new_coffee_shop.drink_size += 1

Have Fun!

Summary

Prototype based inheritance is used in many programming languages, JavaScript being the most well known. As demonstrated in the solutions this week there are a few different techniques that can be used.

lith provided a solution that merges the prototypes data into the inheriting hash when the prototype method is called. Special care is taken to not overwrite fields in the inheriting hash. This use of merge! accomplishes that elegantly:

def prototype=(prototype)
  merge!(prototype) {|k,o,n| o}
end

Another feature of lith’s solution is the ability to use blocks as values in the Hash. This allows the objects to inherit methods as well as simple values.

starbucks = {:drink_size => 3, :cost => lambda {|this| this.drink_size + 1}}
new_coffee_shop = {:drink_size => 1}
new_coffee_shop.prototype = starbucks

In this case the newcoffeeshop’s cost will be 2. It inherits the cost method from the parent.

David Masover’s solution used the JavaScript style of prototype inheritance, where rather than merging the values of the parent into the child object, the child object maintains a reference to the parent. An advantage of this technique is that updates in the parent are carried through to the child, unless the child overrides that property. There is a trade off however, maintaining the link back to the parent means an extra method lookup. This isn’t usually an issue, but if you are working with a deep nesting and inheriting many attributes it can add up, so it’s important to be aware of.

Thorsten Hater submitted a simple solution that maintained a reference to a copy of the prototype object. This prevents modifications in the prototype from being reflected in the child. The simplicity of the implementation comes at a cost though, duplicating some objects may be very expensive.

Matthias Reitinger also submitted a solution this week. Matthias’s solution extends OpenStruct to handle converting method calls into hash keys. Like David’s solution Matthias’s solution maintains a reference to the prototype object. In addition Matthias gives us a prototypes method that returns an array of all the prototypes for this object. This is used to detect a circular inheritance chain, which almost always means an error. Matthias also adds a method to delete a property. When a property is deleted from a child the child’s prototype’s property will shine through.

There were several interesting takes on this week’s quiz. Some of the decisions, like whether to have fixed prototypes by copying/merging or dynamic prototypes by maintaining references are mutually exclusive, but other techniques from these solutions can be combined together, like allowing blocks, deleting properties, or maintaining a list of prototypes. When deciding which type of inheritance best fits your next applications needs it is important to consider the trade offs between simplicity and efficiency as well as speed and memory.

Thank you lith, David, Thorsten and Matthias for your solutions to this week’s quiz!

Prototype-Based Inheritance (#214) - Solutions


Sunday, July 19, 2009