开发者

Kernel#__method__ doesn't seem to work correctly in dynamically defined methods

开发者 https://www.devze.com 2023-02-13 10:38 出处:网络
I\'ve been trying to dynamically define some instance methods in Ruby 1.9. Here\'s 开发者_JAVA技巧the code I\'ve been using to try this out:

I've been trying to dynamically define some instance methods in Ruby 1.9. Here's 开发者_JAVA技巧the code I've been using to try this out:

class Testing
  [:one, :two].each do |name|
    define_method(name) do
      puts __method__
    end
  end
end

And here's the output:

ruby-1.9.2-p180 :008 > t = Testing.new
 => #<Testing:0x00000100961878> 
ruby-1.9.2-p180 :009 > t.one
two
 => nil 
ruby-1.9.2-p180 :010 > t.two
two
 => nil 
ruby-1.9.2-p180 :011 > 

I would expect the result to be one and two respectively. If I call define_method of each one outside of the iteration it works as expected. What am I not understanding here?

Here is one of many examples I saw around online of define_method being called in an iteration. Dynamically defined setter methods using define_method?

What's missing?

Also: Using __method__ isn't critical for me, but it was the best way I could show, that it seems like only the last block sent to define_method is being used for the defined methods. Maybe that is starting to explain the problem to me, but I still don't understand..


Nice find on the weird behavior. Of all the Rubies I tested, only MRI 1.9.2 demonstrates this.

Ryan Davis has reported the bug on the ruby-core list (referencing this question).


You can use something like this instead of define_method:

class Testing
  [:one, :two].each do |name|
    eval <<-EOM 
        def #{name}
            puts __method__
        end
    EOM
  end
end

t = Testing.new
t.one #=> "one"
t.two #=> "two"


The reason this happens is that define_method defines a method in a slightly different way than def does. It has to do with creating anonymous procs and lambdas. What I would suggest is to simply use the method name since you already have it. This should avoid having to search the stack trace for the method name as well, so it should perform better:

class Testing
  [:one, :two].each do |name|
    define_method name do
      "This method's name is #{name}."
    end
  end
end

Testing.new.one
=> This method's name is one.
Testing.new.two
=> This method's name is two.

To clarify, notice what is returned by the following two statements:

class Testing
  define_method :one do
    __method__
  end
end
=> #<Proc:0x000001009ebfc8@(irb):54 (lambda)>

class Testing
  def one
    __method__
  end
end
=> nil

P.S: There's also a performance difference between using the two formats. You can verify yourself that def is faster than define_method using Benchmark.

0

精彩评论

暂无评论...
验证码 换一张
取 消