2010/08/07

instance_variable

http://midorex.blogspot.com/2010/07/attraccessor.html
http://midorex.blogspot.com/2010/08/singletonclass.html
http://midorex.blogspot.com/2010/08/selfclass.html
これらに関連して...

class Test
  @x = 'x'
  class << self
    attr_accessor :x
  end
end

class Test1
  def initialize
    self.class.singleton_class.send(:attr_accessor, :x)
    self.class.x = 'x1'
  end
end

t, t1 = Test.new, Test1.new
puts "t = Test.new, t1 = Test1.new"
puts "t.class.x=> #{t.class.x}"
puts "t1.class.x=> #{t1.class.x}"
puts "t.class.instance_variables=> #{t.class.instance_variables}"
puts "t1.class.instance_variables=> #{t1.class.instance_variables}"

結果

t = Test.new, t1 = Test1.new
t.class.x=> x
t1.class.x=> x1
t.class.instance_variables=> [:@x]
t1.class.instance_variables=> [:@x]

以上をふまえて

class Test オブジェクトと
それを new した オブジェクトと
class Test の特異クラスになったオブジェクト
この3つのオブジェクトにそれぞれ @x と attr_accessor, :x を定義してみる練習

  4 class Test
  5   @x = 'x'
  6   class << self
  7     @x = 'singleton-xxx'
  8     attr_accessor :x
  9     self.class.send(:attr_reader, :x)
 10   end
 11   def initialize
 12     @x = 'ini-xyz'
 13   end
 14   attr_accessor :x
 15 end
 16
 17 t  = Test.new
 18 puts "t = Test.new"
 19 puts "t.x=> #{t.x}"
 20 puts "t.instance_variables=> #{t.instance_variables}"
 21 puts "--\n"
 22 puts "t.class.x=> #{t.class.x}"
 23 puts "t.class.instance_variables=> #{t.class.instance_variables}"
 24 puts "--\n"
 25 s = t.class.singleton_class
 26 puts "s = t.class.singleton_class"
 27 puts "s=> #{s}"
 28 puts "s.x =>#{s.x}"
 29 puts "s.instance_variables=> #{s.instance_variables}"

実行結果

t = Test.new
t.x=> ini-xyz
t.instance_variables=> [:@x]
--
t.class.x=> x
t.class.instance_variables=> [:@x]
--
s = t.class.singleton_class
s=> #<Class:Test>
s.x =>singleton-xxx
s.instance_variables=> [:@x]


line5 の @x を外から読めるようにしているのは
line8 attr_accessor :x

同様に
line7 の@x は
line9 の self.class.send(:attr_reader, :x)

line12 の@x は
line14 attr_reader :x

おもしろいことに

  4 class Test
  5   @x = 'x'
  6   class << self
  7     @x = 'singleton-xxx'
  8     attr_accessor :x
  9     # self.class.send(:attr_reader, :x)
 10   end
 11   def initialize
 12     @x = 'ini-xyz'
 13     self.class.class.send(:attr_accessor, :x)
 14   end
 15   attr_accessor :x
 16 end

line9 のかわりに line13 にしても同じ結果を得ることができた。

+++ memo +++

あたりまえだけども
class Test
attr_reader :x
def initialize
@x = 'ini-xyz'
end
end
t = Test.new
t.x
=>'ini-xyz'
こうすると class Test のインスタンスが定義したインスタンス変数に代入された値を得られる。

new した class Test のインスタンスではなくて、
class Test そのものや、class Test の特異クラスにも
それぞれインスタンス変数を定義できるしそれらのアクセサを定義できるということを
『プログラミングRuby1.9 言語編』(ISBN: 978-4-274-068096) P.351 から学ぶことができた。

# P.351 あたりをうろうろしたままぜんぜん先に進んでいない。それにしても暑い。

self.class

<http://midorex.blogspot.com/2010/08/singletonclass.html> 前回からの続き

class Test
   def initialize(h)
     h.each{|k,v|
       self.class.send(:attr_accessor, k)
       self.send("#{k}=", v)
     }
  end
end

h={:cat=>'cat'}
t = Test.new(h)
t.cat = 'ddd'
p t

これを実行すると
#<Test:0x00000 @cat="ddd">
となる。でもでも :cat を attr_reader にしたかった場合はこのままだとうまくいかない。んで考えてみたこと。

まずは通常の書き方

class Test
  attr_reader :cat, :dog
  def initialize
    @cat = 'meow'
    @dog = 'bowwow'
  end
end

t = Test.new
p t.instance_variables
p t.class.instance_methods[0..2]
p t.cat
t.cat = "cat"

実行結果

[:@cat, :@dog]
[:cat, :dog, :nil?]
"meow"
./draft.rb:15:in `<main>': undefined method `cat=' for #<Test:0x00000 @cat="meow", @dog="bowwow"> (NoMethodError)

最後の行でcat に代入しようとしても attr_reader だから代入できないことを確認。

この結果と同じ結果を導きだすには?

class Test1
  def initialize(h)
    h.each{|k,v|
      self.class.send(:attr_reader, "#{k}")
      self.send("#{k}=", v)
    }
  end
end

h = {:cat=>'meow', :dog=>'bowwow'}
t = Test1.new(h)
p t.instance_variables
p t.class.instance_methods[0..2]
p t.cat
t.cat = "cat"

実行結果

... `block in initialize': undefined method `cat=' for #<Test1:0x00000> (NoMethodError)

self.send("#{k}=", v) は attr_accessor の時は使えるけど attr_reader の時は使えない。
文字通り reader だから getter であって setter じゃないため instance 変数に"cat"を代入できないためにエラーがかえってきた。
ならば
self.send のかわりに普通に instance_variable_set を使ってみた。

class Test1
  def initialize(h)
    h.each{|k,v|
      instance_variable_set("@#{k}", v)
      self.class.send(:attr_reader, "#{k}")
    }
  end
end

h = {:cat=>'meow', :dog=>'bowwow'}
t = Test1.new(h)
p t.instance_variables
p t.class.instance_methods[0..2]
p t.cat
t.cat = "cat"

実行結果

[:@cat, :@dog]
[:cat, :dog, :nil?]
"meow"
./draft.rb:25:in `<main>': undefined method `cat=' for #<Test:0x00000 @cat="meow", @dog="bowwow"> (NoMethodError)

うまくいった。
instance_variable_set でインスタンス変数に値を代入しておいてその後に、getter を設定した。
self.class.send(:attr_reader, "#{k}")
を削除すると、[通常の書き方]から attr_reader を削除した場合と等しくなる。

# --------------------

次はインスタンス変数を定義しないし attr も必要ない場合。
定義したいのはinstance_method def cat と def dog だけ。

通常の書き方。

class Test
  def initialize
  end
  def cat
    "meow"
  end
  def dog
    "bowwow"
  end
end

t = Test.new
p t.instance_variables
p t.class.instance_methods[0..2]
p t.cat

実行結果

[]
[:cat, :dog, :nil?]
"meow"

上記と同じ結果を導くには?

class Test1
  def initialize(h)
    h.each{|k,v|
      self.class.send(:define_method, k){v}
    }
  end
end

h = {:cat=>'meow', :dog=>'bowwow'}
t = Test1.new(h)
p t.instance_variables
p t.class.instance_methods[0..2]
p t.cat

実行結果

[]
[:cat, :dog, :nil?]
"meow"


まとめ
1, getter setter をinitialize 内で使う
self.class.send(:attr_accessor, k)
self.class.send(:attr_reader, k)
self.class.send(:attr_writer, k)

2, @cat のようなインスタンス変数に値を代入する (k はシンボル)
instance_variable_set("@#{k}", v)
# setter が定義されていることが必要条件
self.send("#{k}=", v)

3, def cat のようなインスタンスメソッドを定義する
self.class.send(:define_method, k){v}

+++ 追記 +++

attr_accessor は通常は クラス内の def initialize の上とか下に書く。
initialize の中には書かない。つか普通にエラーになる。
ということは、class Test のinstance が保有するメソッドじゃなくて
class Test が知っているメソッドだとわかるわけだけども
p Test.methods
とかやったって表示されない。じゃ一体どこにあるんだ?ということで下記を作ってみた。

  4 module My
  5   def self.search_method(x, key)
  6     ary = x.methods
  7     print "=== OBJECT:#{x} ===\n"
  8     ary.each{|m|
  9       next unless /methods/.match(m.to_s)
 10       next if m == :methods
 11       eval "a = x.send(m)
 12         if a.index(key)
 13           print \"[:#{key}] exist in #{m}\n\"
 14         else
 15           print \"# #{m}\n\"
 16         end"
 17     }
 18   end
 19 end
 20
 21 class Test
 22   def initialize
 23     My::search_method(self.class, :attr)
 24     # My::search_method(self, :nil?)
 25   end
 26 end
 27
 28 Test.new


# 実行結果

=== OBJECT:Test ===
# instance_methods
# public_instance_methods
# protected_instance_methods
# private_instance_methods
# singleton_methods
# protected_methods
[:attr] exist in private_methods
# public_methods


クラスの「private_methods」に属していることがわかった。
結局...
self.class.send(:attr_accessor, k)
self.send("#{k}=", v)

こういう方法でインスタンス変数を定義するってことは、親の遺伝子を子供が外側から操作しているように思えてきてよくないような気もしてきた。

2010/08/01

singleton_class

昨日のその後

% ruby192 -v
ruby 1.9.2dev (2010-07-11 revision 28618) [x86_64-darwin10.4.0]

module My
  def self.detail(s)
    p s
    puts "ParentName: #{s.class.name}"
    puts "Object_id: #{s.object_id}"
    puts "Methods 1to5: #{s.methods[0..4]}"
    begin
      puts "Method_define? Hello: #{s.class.method_defined?(:hello)}"
      puts "Class.Ancestors: #{s.ancestors}"
      puts "Superclass: #{s.superclass}"
      puts "Singleton_class #{s.singleton_class}"
      puts "Singleton_class #{s.singleton_class.object_id}"
    rescue
    end
    puts "\n"
  end
end
class Test1
  @@top = self
  puts "# -- main of class Test1 --"
  My::detail(self)
  class << self
    @@top_self = self
    puts "# -- class << self of class Test1 --"
    My::detail(self)
  end
  def initialize
    puts "# -- def initialize of class Test1 --"
    My::detail(self)
    puts "="*10
    puts "# TEST: @@top.singleton_class.equal?(@@top_self)"
    p @@top.singleton_class.equal?(@@top_self)
    puts "#"*5
    puts "self.class:: #{own1 = self.class}"
    puts "self.class.singleton_class::  #{own2 = self.class.singleton_class}"
    puts "# TEST: @@top.equal?(self.class)"
    p @@top.equal?(own1)
    puts "# TEST: @@top_self.equal?(self.class.singleton_class)"
    p @@top_self.equal?(own2)
  end
  def hello
    return self
  end
end
t = Test1.new.hello


実行結果

# -- main of class Test1 --
Test1
ParentName: Class
Object_id: 2151885720
Methods 1to5: [:allocate, :new, :superclass, :freeze, :===]
Method_define? Hello: false
Class.Ancestors: [Test1, Object, Kernel, BasicObject]
Superclass: Object
Singleton_class #<Class:Test1>
Singleton_class 2151885700

# -- class << self of class Test1 --
#<Class:Test1>
ParentName: Class
Object_id: 2151885700
Methods 1to5: [:nesting, :constants, :allocate, :new, :superclass]
Method_define? Hello: false
Class.Ancestors: [Class, Module, Object, Kernel, BasicObject]
Superclass: #<Class:Object>
Singleton_class #<Class:#<Class:Test1>>
Singleton_class 2151884980

# -- def initialize of class Test1 --
#<Test1:0x00000100864970>
ParentName: Test1
Object_id: 2151883960
Methods 1to5: [:hello, :nil?, :===, :=~, :!~]
Method_define? Hello: true

==========
# TEST: @@top.singleton_class.equal?(@@top_self)
true
#####
self.class:: Test1
self.class.singleton_class::  #<Class:Test1>
# TEST: @@top.equal?(self.class)
true
# TEST: @@top_self.equal?(self.class.singleton_class)
true


# TEST: @@top.singleton_class.equal?(@@top_self)
は一体何をやっているのか?

Test1 直下における self が .singleton_class メソッドを呼び出すと
#<Class:Test1>
が得られる。
その #<Class:Test1> は、class << self した self 自身と同等のオブジェクトだろうか?
これを確かめたかった。

結果は ture だった
実際

# -- main of class Test1 --
...略

Singleton_class #<Class:Test1>
Singleton_class 2151885700

# -- class << self of class Test1 --
#<Class:Test1>
ParentName: Class
Object_id: 2151885700

同じobject_id をさしている。

一方
initialize の中から呼び出した
self.class と
self.class.singleton_class
に関しても 同様に true がかえってきた。

# TEST: @@top.equal?(self.class)
true
# TEST: @@top_self.equal?(self.class.singleton_class)
true


# TEST: detail(self.class) in initialize もこの(true)結果を裏付けた。

own1 に代入された self.class はTest1 である。
それは class Test1 直下の self と同等。その証拠に同じobject_id を指している。
own2 に代入された self.class.singleton_class は、#<Class:Test1>である。
それは class << self 内の self と同等。同じobject_id を指している。

あらためて...
Test1.new して出来上がったオブジェクトは、class Test1 自身 のインスタンスだってことを思い知らされた。
class Test1 直下のself と、Test1.new されインスタンスとして登場した self とはそれぞれ異なるオブジェクト。
その証拠に異なる object_id を持つ。

class Test1
class << self
end
end

この時点で2つのオブジェクト(Test1 と #<Class:Test1>)にアクセスできる準備ができており
Test1.new することでインスタンスが生成されて3つめのオブジェクトができあがる、と。