Tricking that old, picky interpreter: prototype-based OOP
Ruby's object model has been becoming stricter as of late, preventing some imaginative (albeit ultimately useless?) tricks like the following prototypish OOP:
a = Object.new def a.foo; "a#foo" end a.foo # => "a#foo" ProtoA = Class.new(class << a; self.dup end) b = ProtoA.new b.foo # => "a#foo" RUBY_VERSION # => "1.8.2"
Nowadays, that snippet would die on the Class#dup of the singleton class:
a = Object.new def a.foo; "a#foo" end a.foo # => "a#foo" ProtoA = Class.new(class << a; self.dup end) b = ProtoA.new b.foo # => # ~> -:4:in `initialize_copy': can't copy singleton class (TypeError) # ~> from -:4
This doesn't mean we cannot do it, though, but it requires some black magic:
a = "foo" def a.bar; "A#bar" end proto = a.prototype proto # => #<Class:0xb7d43f50> proto.superclass # => #<Class:#<String:0xb7d45544>> orig = String.instance_methods proto.instance_methods(true) - orig # => ["bar"] ueber_string = proto.new ueber_string # => "" ueber_string.bar # => "A#bar" proto.class_eval do def initialize(x); super(x.to_s.upcase) end end proto.new("hello, world") # => "HELLO, WORLD" object = Object.new class << object def foo; "object#foo" end end obj = object.prototype.new obj.foo # => "object#foo" def object.bar; "object#bar" end obj.bar # => "object#bar"
That's more powerful than a mere Class.new(singleton_class.dup) because changes in the singleton class affect descendents, as happens with normal inheritance (for both classes and modules).
Making it happen
As you might have guessed, the magic Object#prototype method used in the above snippet relies on the same conjuration as evil.rb, which I already used to unfreeze objects and to mess with class hierarchies.
Being built on top of Ruby/DL, as usual we need to declare the types of the internal structures we'll modify on to begin with:
require 'dl/struct' module Internal extend DL::Importable typealias "VALUE", nil, nil, nil, "unsigned long" typealias "ID", nil, nil, nil, "unsigned long" Basic = ["long flags", "VALUE klass"] RBasic = struct Basic RObject = struct(Basic + ["st_table *iv_tbl"]) RClass = struct(Basic + [ "st_table *iv_tbl", "st_table *m_tbl", "VALUE super" ]) def self.critical begin old_critical = Thread.critical Thread.critical = true disabled_gc = !GC.disable yield ensure GC.enable if disabled_gc Thread.critical = old_critical end end end
Internal.critical is used to prevent unwanted changes due to GC or other threads while we're changing low-level stuff: were the rest of ruby to see an intermediate state of the structures we're changing, we'd crash in no time.
Immediate objects
Immediate objects have no singleton class so it doesn't make sense to try to use it as a prototype: in that case, we can just return self.class instead of giving up right away*1. This is what we'll do with Fixnums, Symbols, true, false and nil:
class Object def immediate? [Fixnum, Symbol, NilClass, TrueClass, FalseClass].any?{|klass| klass === self} end end
Tricking ruby
The easiest way to subclass a singleton class is turning it into a normal one and letting ruby create a new one inheriting from it normally, before restoring the flag indicating singleton-ness.
module Internal T_ICLASS = 0x04 T_MODULE = 0x05 T_MASK = 0x3f FL_FREEZE = 1 << 10 FL_SINGLETON = 1 << 11 end class Object def prototype return self.class if immediate? sklass = class << self; self end begin internal_sklass = Internal::RClass.new(DL::PtrData.new(sklass.object_id * 2)) rescue RangeError internal_sklass = Internal::RClass.new(DL::PtrData.new(2 ** 32 + sklass.object_id * 2)) end ret = nil Internal.critical do begin old_flags = internal_sklass.flags internal_sklass.flags &= ~Internal::FL_SINGLETON ret = Class.new(sklass) ensure internal_sklass.flags = old_flags end end ret end end
*1 this doesn't mean we can do much with the returned value, though. For instance, there is no Fixnum.new, so it wouldn't be possible to instantiate 42.prototype
Keyword(s):[blog] [ruby] [prototype] [oop] [dl] [singleton] [evil.rb]
References:[evil.rb wants love]