Delayed Instantiation - delayed.rb

Here's some code I wrote to delay object instantiation until an
object's methods are needed. If there's anything fooey about it, or
if there's a better version out there, please let me know :slight_smile:

--begin--

···

#########################################################################
#########################################################################
#### delayed.rb: Thunks for Object Insantiation
####
#### Author: Matthew Maycock
####
#### Email: _mr_bill_98@yahoo.com
####
#### Date: June 16th, 2004 AD!
####
####
####
#### Purpose:
####
#### Delay instantiation of objects until needed.
####
#### Overhead given by an extra indirection of dispatch
####
#### Use at your own risk!
####
####
####
#### License:
####
#### The least restrictive license applicable to this software
####
#### given that it was written in ruby and uses the ruby library.
####
#### I don't really know that much about such things, so if there
####
#### isn't anything in the way, consider this released under the
####
#### public domain, free for all!
####
#########################################################################
#########################################################################

# Delay creation of an object until some method tries to use it.
class DelayedInstance < Object
  def initialize(klass, *args)
    @object = Object.new
    ivars = Object.instance_method(:instance_variables)
    ieval = Object.instance_method(:instance_eval)
    iclss = Object.instance_method(:class)
    inspt = Object.instance_method(:inspect)

    switched = @delayed_switched = false

    switch = @delayed_switch = Proc.new {
      raise "Already switched!" if switched
      switched = @delayed_switched = true
      @object = klass.send(:new, *args)
    }

    delayed_self = self
    @object.instance_eval {
      @delayed_parent = delayed_self
      @delayed_switch = switch
      @delayed_added = false
      @delayed_eval = ieval
    }

    [:==, :===, :=~, :__id__, :__send__, :class, :clone, :display,
:dup, :eql?, :equal?, :extend, :freeze, :frozen?, :hash, :id,
     :inspect, :instance_eval, :instance_of, :instance_variable_get,
:instance_variable_set, :instance_variables, :is_a?, :kind_of?,
     :method, :methods, :new, :nil?, :object_id, :private_methods,
:protected_methods, :public_methods, :remove_instance_variable,
     :respond_to?, :send, :singleton_method_added,
:singleton_method_removed, :singleton_method_undefined,
:singleton_methods, :taint,
     :tainted?, :to_a, :to_s, :type, :untaint].each {|sym|
      eval <<-END_EVAL
        def @object.#{sym.to_s}(*args, &block)
          return super(*args, &block) unless @delayed_added
          parent = @delayed_parent
          @delayed_switch[]
          obj = @delayed_eval.bind(parent).call { @object }
          obj.send(#{sym.inspect}, *args, &block)
        end
      END_EVAL
    }

    Object.instance_method(:instance_eval).bind(@object).call
{@delayed_added = true}
  end

  def method_missing(sym, *args, &block)
    @delayed_switch[] unless @delayed_switched
    @object.send(sym, *args, &block)
  end
end

[:==, :===, :=~, :__id__, :__send__, :class, :clone, :display, :dup,
:eql?, :equal?, :extend, :freeze, :frozen?, :hash, :id,
:inspect, :instance_eval, :instance_of, :instance_variable_get,
:instance_variable_set, :instance_variables, :is_a?, :kind_of?,
:method, :methods, :new, :nil?, :object_id, :private_methods,
:protected_methods, :public_methods, :remove_instance_variable,
:respond_to?, :send, :singleton_method_added,
:singleton_method_removed, :singleton_method_undefined,
:singleton_methods, :taint,
:tainted?, :to_a, :to_s, :type, :untaint].each {|sym|
  DelayedInstance.module_eval <<-END_EVAL
    def #{sym.to_s}(*args, &block)
      @object.send(#{sym.inspect}, *args, &block)
    end
  END_EVAL
}

if __FILE__ == $0 then

  class Test
    def initialize(index)
      $stdout.puts "Something that takes a thousand hours..."
      @index = index
    end

    def display
      $stdout.puts @index
    end
  end

  data = (1..10).map {|i| DelayedInstance.new(Test, i)}
  data.each {|d| d.display}

  $stdout.puts
  foo = DelayedInstance.new(Test, 1000)
  foo.instance_eval {
    $stdout.puts "Instance Eval!"
    @santa = 100
  }

  $stdout.puts foo.instance_variables.inspect
  $stdout.puts foo.instance_eval {@santa}
end

=begin
### ruby -v: ruby 1.8.1 (2003-12-25) [i386-mswin32]
# OUTPUT
Something that takes a thousand hours...
1
Something that takes a thousand hours...
2
Something that takes a thousand hours...
3
Something that takes a thousand hours...
4
Something that takes a thousand hours...
5
Something that takes a thousand hours...
6
Something that takes a thousand hours...
7
Something that takes a thousand hours...
8
Something that takes a thousand hours...
9
Something that takes a thousand hours...
10

Something that takes a thousand hours...
Instance Eval!
["@index", "@santa"]
100
=end

Sorry for the double post earlier (google issues) and the poorly
formatted code. The code is available at
http://www.cs.drexel.edu/~ummaycoc/delayed.rb

I have run into a problem as I developed this a little further:

on line 97, which is the commented on in the following context

        def self.singleton_method_added(sym)
          return if @skip

          mget = Object.instance_method(:method).bind(self)
          m = mget.call(sym).unbind
          @object.instance_eval {
            @_____meth_hash_____[sym] = m
          }

          code = <<-END_EVAL
            def @object.#{sym}(*a, &b)
              m = @_____meth_hash_____[#{sym.inspect}]
------------> #m.bind(self).call(*a, &b) unless m.nil?
            end

            class << self
              undef_method(#{sym.inspect})
            end
          END_EVAL
          $stdout.puts code # :frowning:
          eval code # :frowning:
        end

I need to make a singleton method out of an object. I can't just
redirect to the parent when a singleton is made, as the parent doesn't
have the same state as the child - nor should it. My current issue is
just finding a way to change what a singleton method is bound to
binding wise for looking up self->instance_variables... (not doing so
lets them be set/used from the DelayedInstance object instead of the
@object object).

I'd most like a way to do this without just fixing the given line
(line 97 in the entire file - which is given below and at the
aforementioned website) - I dislike the idea of having to have the
@_____meth_hash_____ object, but I think that if anything will work,
it's prolly gonna be some hack similar to that. Any help is
appreciated...

-begin

···

###################################################################
###################################################################
### ###
### delayed.rb: Thunks for Object Insantiation ###
### Author: Matthew Maycock ###
### Email: _mr_bill_98@yahoo.com ###
### Date: June 16th, 2004 AD! ###
### ###
### Purpose: ###
### Delay instantiation of objects until needed! ###
### Overhead given by an extra indirection of dispatch! ###
### Use at your own risk! ###
### ###
### License: ###
### The least restrictive license that's applicable to this ###
### software given that it was written in ruby and uses the ###
### ruby library. I don't really know that much about such ###
### things, so if there isn't anything in the way, consider ###
### this released under the public domain, free for all! ###
### ###
### Problems: ###
### eval redefining __id__ and __send__ produces warnings ###
### for now, they are not redefined. I don't know how that ###
### will play out for everything else, however. ###
### ###
### problem with singleton methods and how to pass them on ###
### ###
###################################################################
###################################################################

require 'thread'

# Delay creation of an object until some method tries to use it.
class DelayedInstance < Object
  @@inst_rejects = [:__id__, :__send__].freeze
  @@clss_rejects = [:__id__, :__send__,
:singleton_method_added].freeze

  @@symbols = ([
    :==, :===, :=~, :__id__, :__send__, :class, :clone, :display,
    :dup, :eql?, :equal?, :extend, :freeze, :frozen?, :hash,:id,
    :inspect, :instance_eval, :instance_of, :instance_variable_get,
    :instance_variable_set, :instance_variables, :is_a?, :kind_of?,
    :method, :methods, :new, :nil?, :object_id, :private_methods,
    :protected_methods, :public_methods, :remove_instance_variable,
    :respond_to?, :send, :singleton_method_added,
    :singleton_method_removed, :singleton_method_undefined,
    :singleton_methods, :taint, :tainted?, :to_a, :to_s, :type,
    :untaint
  ]).freeze

  def DelayedInstance.symbols(sym = :class)
    @@symbols - (sym == :class ? @@clss_rejects : @@inst_rejects)
  end

  def DelayedInstance.notify(&notify)
    @@notify = notify
  end
  @@notify = nil

  def initialize(klass, *kargs, &kblock)
    @object = Object.new
    @skip = false
    ieval = Object.instance_method(:instance_eval)
    self_id = self.id
    switched = false
    mutex = Mutex.new

    switch = Proc.new {
      mutex.synchronize {
        next if switched
        @@notify[self_id] unless @@notify.nil?
        switched = true
        @object = klass.send(:new, *kargs, &kblock)

        @skip = true
        @object.instance_eval {
          @_____meth_hash_____ = Hash.new
        }
        def self.method_missing(s, *a, &b)
          @object.send(s, *a, &b)
        end

        def self.singleton_method_added(sym)
          return if @skip

          mget = Object.instance_method(:method).bind(self)
          m = mget.call(sym).unbind
          @object.instance_eval {
            @_____meth_hash_____[sym] = m
          }

          code = <<-END_EVAL
            def @object.#{sym}(*a, &b)
              m = @_____meth_hash_____[#{sym.inspect}]
              #m.bind(self).call(*a, &b) unless m.nil?
            end

            class << self
              undef_method(#{sym.inspect})
            end
          END_EVAL
          $stdout.puts code # :frowning:
          eval code # :frowning:
        end
        @skip = false
      }
    }

    delayed_self = self
    @object.instance_eval {
      @delayed_switch = switch
      @delayed_added = false
      @delayed_eval = ieval.bind(delayed_self)
    }

    DelayedInstance.symbols(:instance).each {|sym|
      eval <<-END_EVAL
        def @object.#{sym.to_s}(*args, &block)
          return super(*args, &block) unless @delayed_added
          @delayed_switch[]
          @delayed_eval.call {
            @object
          }.send(#{sym.inspect}, *args, &block)
        end
      END_EVAL
    }

    Object.instance_method(:instance_eval).bind(@object).call {
      @delayed_added = true
    }
  end

  def method_missing(sym, *args, &block)
    @object.id
    method_missing(sym, *args, &block)
  end

  def singleton_method_added(sym)
    return if @skip
    @object.id
    m = Object.instance_method(:method).bind(self).call(:singleton_method_added)
    res = m.call(sym)
  end
end

DelayedInstance.symbols(:class).each {|sym|
  DelayedInstance.module_eval <<-END_EVAL
    def #{sym.to_s}(*args, &block)
      @object.send(#{sym.inspect}, *args, &block)
    end
  END_EVAL
}

if __FILE__ == $0 then

  DelayedInstance.notify {|obj_id|
    $stdout.puts "Switching with object #{obj_id}"
  }

  class Test
    def initialize(index)
      $stdout.puts "Something that takes a thousand hours..."
      @index = index
    end

    def display_data
      $stdout.puts @index
    end
  end

  $stdout.puts "Building array..."
  data = (1..4).map {|i| DelayedInstance.new(Test, i)}

  i = data[0]

  $stdout.puts "Displaying Items..."
  data.each {|d|
    d.display_data
    $stdout.puts
  }

  $stdout.puts
  foo = DelayedInstance.new(Test, 1000)
  foo.instance_eval {
    $stdout.puts "Instance Eval!"
    @santa = 100
  }
  $stdout.puts

  ivars = Object.instance_method(:instance_variables)
  ieval = Object.instance_method(:instance_eval)

  $stdout.puts
  $stdout.puts "Variables!"
  $stdout.puts foo.instance_variables.inspect
  $stdout.puts foo.instance_eval {@santa}
  $stdout.puts
  $stdout.puts ivars.bind(foo).call.inspect
  $stdout.puts ieval.bind(foo).call {@santa}

  $stdout.puts
  $stdout.puts "Define Singleton!"
  def foo.meow
    self.class.to_s
    @gagaga = 4
  end

  $stdout.puts
  $stdout.puts "Singleton, then Variables!"
  $stdout.puts foo.meow # UnboundMethod#inspect for now.

  $stdout.puts foo.instance_variables.inspect
  $stdout.puts foo.instance_eval {@gagaga}
  $stdout.puts
  $stdout.puts ivars.bind(foo).call.inspect
  $stdout.puts ieval.bind(foo).call {@gagaga}

end

=begin
###################################################################
### OUTPUT
Building array...
Displaying Items...
Switching with object 22220152
Something that takes a thousand hours...
1

Switching with object 22193320
Something that takes a thousand hours...
2

Switching with object 22656588
Something that takes a thousand hours...
3

Switching with object 22629756
Something that takes a thousand hours...
4

Switching with object 22601604
Something that takes a thousand hours...
Instance Eval!

Variables!
["@_____meth_hash_____", "@index", "@santa"]
100

["@skip", "@object"]
nil

Define Singleton!
            def @object.meow(*a, &b)
              m = @_____meth_hash_____[:meow]
              #m.bind(self).call(*a, &b) unless m.nil?
            end

            class << self
              undef_method(:meow)
            end

Singleton, then Variables!
#<UnboundMethod: #<Class:#<DelayedInstance:0x2b1bf98>>#meow>
["@_____meth_hash_____", "@index", "@santa"]
nil

["@skip", "@object"]
nil
=end