Stephen Cagle
Stephen Cagle

Reputation: 14524

I want to add a singleton method with a closure to a Ruby object

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>

Upvotes: 2

Views: 1470

Answers (3)

James Moore
James Moore

Reputation: 9026

Now that 1.9 is out, you can use define_singleton_method:

jruby --1.9 -S irb
irb(main):019:0> fn = -> { length * 10 }
=> #<Proc:0x77cb8e0f@(irb):19 (lambda)>
irb(main):020:0> s.define_singleton_method :length_times_ten, fn
=> #<Proc:0x77cb8e0f@(irb):19 (lambda)>
irb(main):021:0> s
=> "a string"
irb(main):022:0> s.length_times_ten
=> 80

Upvotes: 2

rampion
rampion

Reputation: 89063

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

Upvotes: 1

Chuck
Chuck

Reputation: 237060

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

Upvotes: 2

Related Questions