Jeroko
Jeroko

Reputation: 313

Some problems with define_method

so I'm trying this code out,

class Colors
  def initialize color
    color.each {|c| color c}
  end

  def color c
    self.class.send(:define_method, "about_#{c}") do
      puts "The color #{c} has an intensity of  #{Random.rand}"
    end
  end
end

a = Colors.new(["orange", "pink", "yellow", "green"])
a.about_pink
a.about_pink
a.about_pink

On my machine I get:

The color pink has an intensity of  0.377090691263002
The color pink has an intensity of  0.8375972769713161
The color pink has an intensity of  0.26820920750202837

the problem is that 4 statements each with a different number are printed. Shouldn't all the statements printed contain the same random number as the method is "defined" only once?

Upvotes: 1

Views: 99

Answers (3)

sawa
sawa

Reputation: 168101

No. The timing in which a block is evaluated is determined by the method that uses it. Some methods (such as tap) evaluate it immediately, some others (such as [].each) would never evaluate it. In your case, send does not do anything to the block, and passes it to define_method, which turns the block into the method body of the method being defined. That means that the block would be evaluated every time the defined method is called.

Upvotes: 1

Alex Siri
Alex Siri

Reputation: 2864

What you want to do, is to evaluate Random when you're defining the method. That way, the value is fixed for the defined method:

class Colors
  def initialize color
    @int = {}
    color.each {|c| color c}
  end

  def color c
    intensity = Random.rand
    self.class.send(:define_method, "about_#{c}") do
      puts "The color #{c} has an intensity of #{intensity}"
    end
  end
end

a = Colors.new(["orange", "pink", "yellow", "green"])
a.about_pink
a.about_pink
a.about_pink

As you can see, I save the result of Random in a variable, which is fixed in the internal context. What happens in your initial example, is that the string that you output gets evaluated in every call, and that evaluation has the call to Random inside of it, so it runs it every time.

Upvotes: 3

Малъ Скрылевъ
Малъ Скрылевъ

Reputation: 16507

Since you always do a call to Random.rand inside your block, the value always will be different because the block code is also always called. So you result is correct. But to keep the value at first time, and reuse it, just store it to a var as follows:

  def initialize color
    @int = {}
    color.each {|c| color c}        
  end

  def color c
    self.class.send(:define_method, "about_#{c}") do
      puts "The color #{c} has an intensity of #{@int[ c ] ||= Random.rand}"
    end
  end

And we will get something like:

The color pink has an intensity of 0.6879417562602181
The color pink has an intensity of 0.6879417562602181
The color pink has an intensity of 0.6879417562602181
The color orange has an intensity of  0.8019817000526268

Upvotes: 1

Related Questions