13
6

More than 5 years have passed since last update.

Rubyのクラスインスタンス変数と特異クラス

Last updated at Posted at 2018-05-29

昼休みに先輩と雑談で話していた内容を自分の勉強がてらメモ
雑多なメモですが、参考になれば幸いです。

追記: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

「初めてのRuby」を書かれたyuguiさんの記事
Rubyのメタクラス階層
http://blog.yugui.jp/entry/768

Ruby 初級者のための class << self の話 (または特異クラスとメタクラス)
https://magazine.rubyist.net/articles/0046/0046-SingletonClassForBeginners.html

13
6
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
6