昼休みに先輩と雑談で話していた内容を自分の勉強がてらメモ
雑多なメモですが、参考になれば幸いです。
追記:scivolaさんのご指摘を受けて、一部変更しました。
環境
Ruby 2.5.0
クラスインスタンス変数
Rubyにはクラスインスタンス変数があります。
クラスインスタンス変数は下記のようなもの。
class A
@a = 1
def instance_m
@a
end
class << self
def class_m
@a
end
end
end
このように記述するとどうなるかというと、
pry(main)> A.new.instance_m
=> nil
pry(main)> A.class_m
=> 1
と class A
のインスタンスメソッドからはアクセスできないが、 class A
のクラスメソッドからはアクセスできる変数となる。これがクラスインスタンス変数。
一般的には多分下記にように、クラスメソッドの中でクラスインスタンス変数を書くのが普通。逆にいうとクラスメソッドの中でインスタンス変数を定義するとクラスインスタンス変数となる。
class A
class << self
def class_m
@a = 1
end
end
end
特異クラス
Rubyにおいてクラスメソッドは特異メソッドであり、公式ドキュメントにもそのように書かれている。
https://docs.ruby-lang.org/ja/latest/doc/spec=2fdef.html
(クラスメソッドの定義欄参照)
Ruby におけるクラスメソッドとはクラスの特異メソッドのことです。
Ruby では、クラスもオブジェクトなので、
普通のオブジェクトと同様に特異メソッドを定義できます。
よく知られている話だが、クラスメソッド(=特異メソッド)には二つの定義方法がある。
class B
def self.hoge
puts "hoge"
end
class << self
def fuga
puts "fuga"
end
end
end
このように定義すると。クラスメソッドが定義される。
pry(main)> B.hoge
hoge
=> nil
pry(main)> B.fuga
fuga
=> nil
前者の方法は特異メソッドに直接定義する方法であり、後者の方法は特異クラスを開き、その中でメソッド定義をする方法となっている。どちらにせよクラスメソッドを定義することが可能であり、 class << self
によって開かれているのが特異クラス。
ここで疑問に思ったこと
それでは特異クラスの中でクラスインスタンス変数を定義するとどうなるのであろうか?試しに定義してみる。
class C
@c = 1
class << self
@c = 2
def class_m
@c
end
end
end
こうするとどうなるかというと…
pry(main)> C.class_m
=> 1
当たり前と言えば当たり前なのだが、特異クラスで定義したクラスインスタンス変数は参照されず、 class C
のクラスインスタンス変数 @c = 1
が参照されている。
つまり、 @c = 2
はクラスメソッドからもインスタンスメソッドからもアクセスできない存在となる。
どうやってアクセスするのか?
上記のようなことは実務では使うことはほとんどないので問題がないと言えば問題がないのだが、やはり気になる…
それではどうやってアクセスするのかというと極めて簡単で下記のように記述する。
class D
@d = 1
class << self
@d = 2
def class_m
@d
end
class << self
def singleton_class_m
@d
end
end
end
end
少し気持ち悪いが class D
の特異クラスの中でさらに特異クラスを開き、その中で特異クラスメソッドを定義する…
こうすると下記のようになる。
pry(main)> D.class_m
=> 1
pry(main)> D.singleton_class.singleton_class_m
=> 2
まず先ほどと同じように class D
のクラスメソッドから参照した場合は、 class D
のクラスインスタンス変数 @d = 1
が呼ばれることになる。
一方で D.singleton_class
とするとDの特異クラスにアクセスすることができる。
class D
の特異クラスの中で、この特異クラスのクラスメソッドで呼び出すと、 class D
の特異クラスのクラスインスタンス変数 @d = 2
が呼ばれることになるのである!
どういうことか
少し頭がこんがらがるかもしれないが、考えてみれば当たり前のことである。
最初にクラスインスタンス変数はクラスメソッドからはアクセスできるが、インスタンスメソッドからはアクセスできないと書いた。
つまりクラスインスタンス変数には、インスタンスメソッドからはアクセスできないのである。
再掲
class A
@a = 1
def instance_m
@a
end
class << self
def class_m
@a
end
end
end
インスタンスメソッドからはクラスインスタンス変数はアクセスできない
pry(main)> A.new.instance_m
=> nil
今回は特異クラスの中でクラスインスタンス変数を定義した。つまり特異クラスのインスタンスメソッド = 元のクラス(今回は class D
)のクラスメソッドになるので、当然、特異クラスのインスタンスメソッド( class D
のクラスメソッド)は、特異クラスのクラスインスタンス変数にはアクセスできない。
つまり class D
のインスタンスメソッドが class D
のクラスインスタンス変数にアクセスできないのと全く同じである。
class A
@a = 1 # ここにはインスタンスメソッドからはアクセスできない。
def instance_m
@a # インスタンス変数が定義されてないのでnilが返る
end
class << self
def class_m
@a # クラスメソッドからは@a = 1にアクセスできる。
end
end
end
特異クラスのクラスインスタンス変数にアクセスするには特異クラスのクラスメソッドを定義する必要があるのである。そのため、特異クラスの中でさらに特異クラス(特異特異クラス?)を開き、 class D
の特異クラスにクラスメソッドを定義することによってアクセスしたということである。
終わりに
特異クラスと特異メソッドについて、しっかりと理解できていなかったが、今回の試行錯誤を通じて少しは理解できた気がする…
Rubyは奥深かくて楽しいので、初心者エンジニアとして少しずつ学んでいきたいと思います。
初心者なので、理解が間違っている点やわかりにくい点がありましたら、ご指摘頂けると大変ありがたく思います。
参考
「初めてのRuby」を書かれたyuguiさんの記事
Rubyのメタクラス階層
http://blog.yugui.jp/entry/768
Ruby 初級者のための class << self の話 (または特異クラスとメタクラス)
https://magazine.rubyist.net/articles/0046/0046-SingletonClassForBeginners.html