Forum: Ruby Prototype-Based Inheritance (#214)

Announcement (2017-05-07): is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see and for other Rails- und Ruby-related community platforms.
33117162fff8a9cf50544a604f60c045?d=identicon&s=25 Daniel X Moore (yahivin)
on 2009-07-10 23:25
(Received via mailing list)

The three rules of Ruby Quiz:

1.  Please do not post any solutions or spoiler discussion for this
quiz until 48 hours have elapsed from the time this message was

2.  Support Ruby Quiz by submitting ideas and responses
as often as you can!

3.  Enjoy!

Suggestion:  A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby Talk follow the discussion.  Please reply to
the original quiz message, if you can.

RSS Feed:


## Prototype-Based Inheritance (#214)

Ayú Rubyists,

This week's quiz is to implement prototype-based Inheritance[1]. 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 =>
    pele_jr = {:name => "Pele Jr."}
    pele_jr.prototype = pele # => "Pele Jr." # => "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!

87ef5d1e14b148eb596433bc17ffe690?d=identicon&s=25 lith (Guest)
on 2009-07-12 19:50
(Received via mailing list)
>     pele = {:name => "Pele", :sport => "football", :position => "Forward"}
>     pele_jr = {:name => "Pele Jr."}
>     pele_jr.prototype = pele
> # => "Pele Jr."
> # => "football"
>     pele_jr.position # => "forward"

What about:

class Hash

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

    def method_missing(name, *args, &block)
        if has_key?(name)
            val = fetch(name)
            case val
            when Proc
                return, *args)
        if name.to_s =~ /^(.*?)=$/
            meth = :[]=
            name = $1.intern
            meth = :[]
        margs = [name, *args]
        margs << block if block
        send(meth, *margs)


This would allow you to do something like:

pele_jr.age = 20 = lambda {|this, a| this.age + a} #=> 30
1bc63d01bd3fcccc36fb030a62039352?d=identicon&s=25 David Masover (Guest)
on 2009-07-13 17:29
(Received via mailing list)
On Sunday 12 July 2009 12:49:55 pm lith wrote:

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

I based my implementation on Javascript, and one of the nice properties
is that the prototype can change later. So, for example, in my

irb(main):005:0> pele.favorite_food = 'hamburger'
=> "hamburger"
irb(main):006:0> pele_jr.favorite_food
=> "hamburger"
irb(main):007:0> pele
=> {:name=>"Pele", :sport=>"football", :position=>"Forward",
irb(main):008:0> pele_jr.favorite_food = 'cheeseburger'
=> "cheeseburger"
irb(main):009:0> pele_jr.favorite_food
=> "cheeseburger"
irb(main):010:0> pele.favorite_food
=> "hamburger"

Of course, this is slower, but more powerful, I think.

>             when Proc
>                 return, *args)
>             end
> This would allow you to do something like:
> pele_jr.age = 20
> = lambda {|this, a| this.age + a}
> #=> 30

Nice touch...

Here's mine:

module Prototype
  attr_accessor :prototype
  def method_missing *args
    prototype.public_send *args

That's more generic than just hashes. Mix it into Object and it works on
anything. Of course, it's not exactly convenient to have to define new
like this:

pele =
class << pele
  attr_accessor :name
end = 'Pele'

It also severely limits what you can do with some of the builtin classes
Fixnums that can't have a metaclass.

And of course, to make it saner for the example (and because OpenStruct
cooperate), I can do this:

class Hash
  include Prototype
  def method_missing name, *args
    if has_key? name
    elsif name =~ /.=$/
      self[name.to_s.chomp('=').to_sym] = args.first
D91f9adf63acfa73b50ebab43f10d7ee?d=identicon&s=25 Thorsten Hater (Guest)
on 2009-07-14 16:05
(Received via mailing list)

a simple Solution, which works for static Prototypes:

module Prototype
  def method_missing name, *args
    # if we have a prototype forward to it's methods
    # else propagate method missing upwards
    if defined?(@__proto__) && @__proto__
      @__proto__.send name,*args

  def set_prototype pt
    # clone the prototype, so we can freely modify
    # it's attributes w/o interference with others
    @__proto__ = pt.clone

As said above this is a simple solution, but it should work for all
and is easily written.
But it comes at the cost of duplicating arbitrarily complex objects
many times) and just allows static prototypes.
I couldn't come up with a solution which filters writing access to the
type (apart from simple regexing) but allows modification from the
Possibly one could use observers and method_defined hooks for this, but
complexity would be rather high on this way.

Best regards,
29194588628f0c08917c93d1b5793d3a?d=identicon&s=25 Matthias Reitinger (Guest)
on 2009-07-20 05:06
(Received via mailing list)
Daniel Moore wrote:
> ## Prototype-Based Inheritance (#214)

I decided to give it a shot:

  require 'ostruct'

  class Prototype < OpenStruct
    attr_reader :prototype

    def prototype=(new_prototype)
      if new_prototype.prototypes.include?(self)
        raise ArgumentError, "circular prototype chain"
      @prototype = new_prototype

    def prototypes
      if @prototype.nil?
        [@prototype] + @prototype.prototypes

    def delete_slot(name)
      name = name.to_sym
      if @table.has_key?(name)
        meta = class << self; self; end
        meta.send(:remove_method, name)
        meta.send(:remove_method, :"#{name}=")

    def method_missing(mid, *args)
      if @prototype.nil? || mid.id2name =~ /=$/
        @prototype.send(mid, *args)

Most of the functionality is inherited from OpenStruct.  The trip down
the prototype chain happens in method_missing.  A short IRB session
should explain the usage:

  >> require 'prototype'
  >> starbucks = => "Starbucks", :drink_size => 3)
  => #<Prototype name="Starbucks", drink_size=3>
  >> new_coffee_shop = => "New")
  => #<Prototype name="New">
  >> new_coffee_shop.prototype = starbucks
  => #<Prototype name="Starbucks", drink_size=3>
  >> new_coffee_shop.drink_size
  => 3
  >> new_coffee_shop.drink_size += 1
  => 4
  >> new_coffee_shop.drink_size
  => 4
  >> new_coffee_shop.delete_slot(:drink_size)
  => 4
  >> new_coffee_shop.drink_size
  => 3
  >> another_coffee_shop = => "Another")
  => #<Prototype name="Another">
  >> another_coffee_shop.prototype = new_coffee_shop
  => #<Prototype name="New">
  >> another_coffee_shop.drink_size
  => 3
  >> another_coffee_shop.prototypes
  => [#<Prototype name="New">, #<Prototype name="Starbucks",
  >> starbucks.prototype = another_coffee_shop
  ArgumentError: circular prototype chain
    from ./prototype.rb:8:in `prototype='
    from (irb):23

33117162fff8a9cf50544a604f60c045?d=identicon&s=25 Daniel X Moore (yahivin)
on 2009-07-26 20:21
(Received via mailing list)
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}

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 new_coffee_shop'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!
This topic is locked and can not be replied to.