tags:

views:

227

answers:

3

Hi all,

Let's go to the code directly :)

#!/usr/bin/ruby
 require 'tk'


class Epg

def initialize
 @var = "bad" 
 @cvs = nil 
 @items_demo = TkRoot.new() {title "EPG"}
 TkFrame.new(@items_demo) {|cf|
    @var = "good" 
    @cvs = TkCanvas.new(cf) {|c|}
  puts  "@cvs 1  is #{@cvs}"
  puts  "@var 1 is #{@var}"
 }.pack('side'=>'top', 'fill'=>'both', 'expand'=>'yes')

 puts  "@cvs 2 is #{@cvs}"
 puts  "@var 2 is #{@var}"

end #initialize

def test
 @var = "bad"
 puts " @var 3 :#{@var}"
 (1..3).each {|x| @var="good"}
 puts " @var 4 :#{@var}"
end
 end

 e= Epg.new 
 e.test

Here are the output :

@cvs 1  is #<Tk::Canvas:0xb7cecb08>
@var 1 is good
@cvs 2 is 
@var 2 is bad           #@var has NOT been changed by the code in the block
@var 3 :bad
@var 4 :good            #@var has been changed by the code in the block

Why we see different behavior here?

A: 

Yes it can because it is an instance variable, so its scope is not limited to the block itself.

Simone Carletti
+4  A: 
Robert Klemme
Did you have a typo? Does it should be "the assignment @var = "good" _did not_ changes the instance variable of TkFrame". As @x is still 10 but not 20 in your example.
pierr
No, there is no typo.
Robert Klemme
YSE.the assignment @var = "good" changes the instance variable of TkFrame but can not change the intance variable of Epg.
pierr
I am not sure how to read your comment. This is exactly what I said, didn't I? So do you agree that there is no typo?
Robert Klemme
+3  A: 

You can think of blocks as closing over both the set of local variables and the current self.

In Ruby, you will always have access to local variables, no matter what. The self encapsulates instance methods on the current object as well as instance variables.

Consider the following code:

class Table
  def initialize(legs)
    @legs = legs
  end

  def with_legs
    yield @legs
  end
end

And then:

def some_calling_method
  name = "Yehuda"
  Table.new(4) {|legs| puts "#{name} gnaws off one of the #{legs} legs" }
end

By Ruby's block semantics, you can be assured that name will be available inside the block, even without looking at the method you're calling.

However, consider the following:

class Person
  def initialize(name)
    @name = name
  end

  def gnaw
    Table.new(4).with_legs do |legs| 
      puts "#{@name} gnaws off one of the #{legs} legs"
    end
  end
end

Person.new("Yehuda").gnaw

In this case, we are accessing the @name instance variable from inside the block. It works great in this case, but is not guaranteed. What if we implemented table a bit differently:

class Table
  def initialize(legs)
    @legs = legs
  end

  def with_legs(&block)
    self.instance_eval(&block)
  end
end

Effectively, what we're saying is "evaluate the block in the context of a different self." In this case, we are evaluating the block in the context of the table. Why would you do that?

class Leg
  attr_accessor :number
  def initialize(number)
    @number = number
  end
end

class Table
  def initialize(legs)
    @legs = legs
  end

  def with_leg(&block)
    Leg.new(rand(@legs).instance_eval(&block)
  end
end

Now, you could do:

class Person
  def initialize(name)
    @name = name
  end

  def gnaw
    Table.new(4).with_leg do
      puts "I'm gnawing off one of leg #{number}"
    end
  end
end

If you wanted access to the person object inside of the block, you'd have to do:

class Person
  def initialize(name)
    @name = name
  end

  def gnaw
    my_name = name
    Table.new(4).with_leg do
      puts "#{my_name} is gnawing off one of leg #{number}"
    end
  end
end

As you can see, the use of instance_eval can make it simpler and less bulky to access methods of a far-off object inside a block, but comes at the cost of making the self unaccessible. The technique is usually used in DSLs, where a number of methods are injected into the block, but the self doesn't matter that much.

This is what's happening with Tk; they're using instance_eval to inject their own self into the block, which is wiping your self clean.

Yehuda Katz
Thanks, Katz. I need some time to digest all you said..
pierr