views:

237

answers:

2

I wish to add a singleton method to a particular object. I wish that when a instance method on a object is first called, it does some work, and then creates a singleton method for said object of the same name (that contains the work). On all subsequent calls on said object, the singleton method would shadow the instance method and would be called.

I know how to create a singleton method, my problem is that I want the singleton method created to call a lambda (l in this case). def does not create a closure, so I cannot reference variable l (code below) when the method is subsequently called (l.call() is commented out in this example) I wish to know how I can create a closure when creating a singleton method on a particular object. Any help would be appreciated. Thank you.

class Thing
end

t = Thing.new
t2 = Thing.new

Thing.instance_eval() do
  def speak
    puts "I speak for all Things, I am a class method"
  end
end

Thing.class_eval() do
  def speak
    puts "This is the instance method referenced by the Thing object #{self}"
    r = "something I wish to hold on to, maybe expensive to calculate"
    l = lambda {puts r}
    instance_eval() do
      def speak()
        puts "This is the singleton method in the Thing object #{self}"
        #l.call() # I want this to work! How?
      end
    end
  end
end

Thing.speak()
t.speak()
t2.speak()
t.speak()
t2.speak()

Gives the following results when run: (I changed '<' to '#' so they show up in html)

I speak for all Things, I am a class method

This is the instance method referenced by the Thing object #Thing:0x1d204>

This is the instance method referenced by the Thing object #Thing:0x1d1dc>

This is the singleton method in the Thing object #Thing:0x1d204>

This is the singleton method in the Thing object #Thing:0x1d1dc>

+2  A: 

You can define a method with a block using define_method.

Example:

class Object
  def eigenclass
    class <<self; self end
  end
end

a = "Hello"
other_word = "World"
a.eigenclass.class_eval do
  define_method(:cliche) {"#{self} #{other_word}"}
end
a.cliche # => "Hello World"
"Goodbye".cliche # => NoMethodError: undefined method `cliche' for "Goodbye":String

Here is an implementation of a define_singleton_method method:

class Object
  def define_singleton_method(name, &block)
    eigenclass = class<<self; self end
    eigenclass.class_eval {define_method name, block}
  end
end
Chuck
Yeah, see the problem seems to be that define_method is a Module/Class module method. I need a define_method like thing for any object. The method you have defined there is defining a instance method for all Ojbect's. I am looking for a way to dynamically (with closures) define (lets call them singleton) methods on individual objects, not instance methods for all objects of a given class. Thanks.
Stephen Cagle
No, it is not defining a method on all objects. It's defining a singleton method. I've updated the example code to illustrate this.
Chuck
Ok, I tried out some permutation of this code, And it seems to work. I must say, I would never have thought to do the "class <<self; self end" trick, that was pretty cool. Anywho, this seems to be a working solution to the problem of creating singleton methods for ruby objects. Thanks.
Stephen Cagle
+1  A: 

Well, one way to do it would be to pack it into an instance variable:

(FYI you can just do class Thing to reopen Thing (it's a little shorter than using #class_eval, and you don't need #instance_eval to define methods from within a method).

class Thing
  def speak
    puts "This is the instance method referenced by the Thing object #{self}"
    r = "something I wish to hold on to, maybe expensive to calculate"
    @l = lambda {puts r}
    instance_eval do 
      def speak()
        puts "This is the singleton method in the Thing object #{self}"
        @l[]
      end
    end
  end
end

This will redefine #speak, but only for that instance of Thing. Other instances of Thing will still have the original definition.

The alternative is, as Chuck pointed out, to use the singleton class (aka metaclass, aka eigenclass) associated with the instance. The singleton class is the object that stores all the singleton methods associated with an object. You can get the context for singleton class evaluation by using the funny class <<object ; ... ; end syntax (similar to the context given by #class_eval by normal classes).

class Thing
  def speak
    puts "This is the instance method referenced by the Thing object #{self}"
    r = "something I wish to hold on to, maybe expensive to calculate"
    singleton_class = class <<self # open singleton class context for current instance
      # in this context, self now refers to the singleton class itself
      self
    end
    l = lambda {puts r}
    singleton_class.class_eval do
      # since we used #class_eval, local variables are still in scope
      define_method(:speak) do 
        puts "This is the singleton method in the Thing object #{self}"
        # since we used #define_method, local variables are still in scope
        l[]
      end
    end
  end
end
rampion