
Object-oriented programming is all about objects. About objects and messages they pass to each other.
Programs contain many objects. Way too many to make each of them by hand. Objects need to be mass-produced. There are basically two ways of mass producing objects.
- The industrial way - building factory objects that build other objects.
- The biological way - building prototype objects that can be cloned.
Most object-oriented languages like Smalltalk and Ruby use the industrial way. The object factories are also known as "class objects" (or even "classes", but that's a bit confusing).
To create a new object factory you do:
a_factory = Class.new()And then:
a_factory.define_instance_method(:hello) {|arg|
puts "Hello, #{arg}!"
}
an_object = a_factory.new()Some languages like Self and The Most Underappreciated Programming Language Ever (TMUPLE, also known as JavaScript), use biological method instead. In biological method you create a prototype, then clone it:
an_object.hello("world")
a_prototype = Object.new()Then:
a_prototype.define_method(:hello) {|arg|
puts "Hello, #{arg}!"
}
an_object = a_prototype.clone()Biological way is less organized, but simpler and more lightweight. There are only objects and messages, nothing more.
an_object.hello("world")
Array is a prototype for all arrays and so on.Industrial way is more organized, but much more complex and heavy. There are objects, classes, class objects, superclasses, inheritance, mixins, metaclasses, singleton classes. It's just too complex.
This complexity exists for a reason, but sometimes we'd really rather get away with it and use something simpler.
Prototype-based programming in Ruby
And in Ruby we can !First, we need to be able to define methods just for individual objects:
def x.hello(arg)Now we just need to copy existing objects:
puts "Hello, #{arg}!"
end
x.hello("world") # => "Hello, world!"
y = x.clone()The objects are independent, so each of them can redefine methods without worrying about everyone else:
y.hello("world") # => "Hello, world!"
z = x.clone()Converting class objects into prototype objects would probably introduce compatibility issues, so let's go halfway there:
def x.hello(arg)
puts "Guten Tag, #{arg}!"
end
def z.hello(arg)
puts "Goodbye, #{arg}!"
end
x.hello("world") # => "Guten Tag, world!"
y.hello("world") # => "Hello, world!"
z.hello("world") # => "Goodbye, world!"
class Class
def prototype
@prototype = new unless @prototype
return @prototype
end
def clone
prototype.clone
end
end
def (String.prototype).hello
puts "Hello, #{self}!"
end
a_string = String.clone
a_string[0..-1] = "world"
a_string.hello #=> "Hello, world!"
Horizontal gene transfer
Of course transfer of genes from parents to offspring is only half of the story. The other half is gene transfer between unrelated organisms.We can easily use delegation and
method_missing, but let's do something more fun instead - directly copying genes (methods) between objects.a_person = Object.newBut Megumi is Japanese, so she needs reversed
class <<a_person
attr_accessor :first_name
attr_accessor :name
def to_s
"#{first_name} #{name}"
end
end
nancy_cartwright = a_person.clone
nancy_cartwright.first_name = "Nancy"
nancy_cartwright.name = "Cartwright"
hayashibara_megumi = a_person.clone
hayashibara_megumi.first_name = "Megumi"
hayashibara_megumi.name = "Hayashibara"
to_s method:Later we find out that another person needs reversed
def hayashibara_megumi.to_s
"#{name} #{first_name}"
end
to_s:inoue_kikuko = a_person.cloneWe want to do something like:
inoue_kikuko.first_name = "Kikuko"
inoue_kikuko.name = "Inoue"
japanese_to_s = hayashibara_megumi.copy_gene(:to_s)OK, first let's fix a few deficiencies of Ruby 1.8.
inoue_kikuko.use_gene japanese_to_s
define_method is private (should be public), andthere is no simple
singleton_class.Both will hopefully be fixed in Ruby 2.
class ObjectAnd now:
def singleton_class
(class <<self; self; end)
end
end
class Class
public :define_method
end
class ObjectWe can try how the gene splicing worked:
def copy_gene(method_name)
[method(method_name).unbind, method_name]
end
def use_gene(gene, new_method_name = nil)
singleton_class.define_method(new_method_name||gene[1], gene[0])
end
end
puts nancy_cartwright #=> Nancy CartwrightIf we try it in Ruby 1.9 we get a different error message:
puts hayashibara_megumi #=> Hayashibara Megumi
puts inoue_kikuko #=> in `to_s':TypeError: singleton method called for a different object
puts inoue_kikuko #=> in `define_method': can't bind singleton method to a different class (TypeError)What Ruby does makes some sense - if method was implemented in C (like a lot of standard Ruby methods), calling it on object of completely different "kind" can get us a segmentation fault. With C you can never be sure, but it's reasonably safe to assume that we can move methods between objects with the same "internal representation".
We need to use Evil Ruby. Evil Ruby lets us access Ruby internals.
UnboundMethod class represents methods not bound to any objects. It contains internal field rklass, and it can only bind to objects of such class (or subclasses). First, let's define a method to change this rklass:class UnboundMethodNow we could completely remove protection, but we just want to loosen it. Instead of classes, we want to compare internal types:
def rklass=(c)
RubyInternal.critical {
i = RubyInternal::DMethod.new(internal.data)
i.rklass = c.object_id * 2
}
end
end
class ObjectAnd voilà!
def copy_gene(method_name)
[method(method_name).unbind, method_name, internal_type]
end
def use_gene(gene, new_method_name = nil)
raise TypeError, "can't bind method to an object of different internal type" if internal_type != gene[2]
gene[0].rklass = self.class
singleton_class.define_method(new_method_name||gene[1], gene[0])
end
end
puts nancy_cartwright #=> Nancy CartwrightThis is merely a toy example. But sometimes prototypes lead to design more elegant than factories. Think about the possibility in your next project.
puts hayashibara_megumi #=> Hayashibara Megumi
puts inoue_kikuko #=> Inoue Kikuko
Full listing
require 'evil'
class Object
def singleton_class
(class <<self; self; end)
end
end
class UnboundMethod
def rklass=(c)
RubyInternal.critical {
i = RubyInternal::DMethod.new(internal.data)
i.rklass = c.object_id * 2
}
end
end
class Class
public :define_method
end
class Object
def copy_gene(method_name)
[method(method_name).unbind, method_name, internal_type]
end
def use_gene(gene, new_method_name = nil)
raise TypeError, "can't bind method to an object of different internal type" if internal_type != gene[2]
gene[0].rklass = self.class
singleton_class.define_method(new_method_name||gene[1], gene[0])
end
end
a_person = Object.new
class <<a_person
attr_accessor :first_name
attr_accessor :name
def to_s
"#{first_name} #{name}"
end
end
nancy_cartwright = a_person.clone
nancy_cartwright.first_name = "Nancy"
nancy_cartwright.name = "Cartwright"
hayashibara_megumi = a_person.clone
hayashibara_megumi.first_name = "Megumi"
hayashibara_megumi.name = "Hayashibara"
def hayashibara_megumi.to_s
"#{name} #{first_name}"
end
inoue_kikuko = a_person.clone
inoue_kikuko.first_name = "Kikuko"
inoue_kikuko.name = "Inoue"
japanese_to_s = hayashibara_megumi.copy_gene(:to_s)
inoue_kikuko.use_gene japanese_to_s
puts nancy_cartwright
puts hayashibara_megumi
puts inoue_kikuko
digg
reddit
del.icio.us
DZone
