2009-07-01 16 views
2

の直接コードに行こう:ブロック内で変数を変更できますか?

ここ
#!/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 

が出力されます。

@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 

我々は、ここでは異なる動作を参照してくださいなぜ?

答えて

4

あなたはローカル変数のセットと、現在のselfの両方の上にクローズなどのブロックと考えることができます。

Rubyでは、は常にに何らかのローカル変数にアクセスできます。 selfは、現在のオブジェクトとインスタンス変数のインスタンスメソッドをカプセル化します。

次のコードを考えてみましょう:

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

    def with_legs 
    yield @legs 
    end 
end 

そして:Rubyのブロックの意味では

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

を、あなたはnameも方法を見ずに、ブロック内で利用可能になることを保証することができますあなたは電話している。

ただし、次の点を考慮してください

この場合
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 

、我々はブロックの中から@nameインスタンス変数にアクセスしています。この場合は素晴らしいですが、保証されていません。私たちは、少し異なるテーブルを実装した場合:

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

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

効果的に、我々はされて言っている「異なる自己のコンテキストでブロックを評価します。」この場合、テーブルのコンテキストでブロックを評価しています。どうしてそうするか?

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 

さて、あなたができる:

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 

を使用すると、ブロックの内部personオブジェクトへのアクセスを望んでいた場合、あなたがしなければならないと思います。

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 

することができますようにinstance_evalを使用すると、ブロック内の遠く離れたオブジェクトのメソッドにアクセスするのが簡単で、かさばりにくくなりますが、selfにアクセスできなくなるという犠牲を払っています。このテクニックは通常、DSLで使用されますが、ブロックにはさまざまなメソッドが注入されていますが、自己はあまり重要ではありません。

これはTkで起こっていることです。彼らはinstance_evalを使用して自分のselfをブロックに注入しています。これはあなたのselfをきれいにしています。

+0

ありがとう、Katz。あなたが言ったすべてを消化する時間が必要です。 – pierrotlefou

4

TkFrame.newはinstance_evalを使用するため、@var = "good"という割り当てはTkFrameのインスタンス変数を変更するという説明です。これを試してみてください:

 
class A 
    def initialize(&b) 
    instance_eval(&b) 
    end 
end 

class B 
    def initialize 
    @x = 10 
    @a = A.new do 
     @x = 20 
    end 
    end 
end 

p B.new 

これはあなたが表示されますものです:

 
#<B:0x10141314 @x=10, @a=#<A:0x10141300 @x=20>> 
+0

入力ミスがありましたか?それは "割り当て@var ="良い "_did not_ TkFrameのインスタンス変数を変更する"でなければなりません。あなたの例では、@xはまだ10ですが、20ではありません。 – pierrotlefou

+0

いいえ、タイプミスはありません。 –

+0

YSE.theの割り当て@var = "good"はTkFrameのインスタンス変数を変更しますが、Epgのintance変数は変更できません。 – pierrotlefou