柴ブログ

プログラミング奮闘記

メタプログラミングRuby2章を読んで調べたこと

はじめに

知人のエンジニアの方達とメタプログラミングRubyの輪読会をし、SmartHRさんで実施された問題を一緒に解いていく中で調べたことをまとめ。

対象リポジトリは下記。今回は02_object_modelに着手。

github.com

学んだメソッド

上記の問題のテストコードで下記メソッドを呼んでいて、見慣れなかったので調べました。(singleton_classclass_eval)

# reading-metaprogramming-ruby/test/02_object_model/test_hierarchy.rb
c4 = C4.new
c4.singleton_class.class_eval do
  private
  def value=(x)
    @called_setter = true
    @value = x
  end

  def value
    @called_getter = true
    if defined?(@value)
      @value
    else
      nil
    end
  end

singleton_class

これはレシーバの特異クラスを返すメソッド。

レシーバの特異クラスがなければ作成する。 上記の例だとc4オブジェクトの特異クラスを作成して返す。

irbで動かすと下記のようになる。

irb(main):001:1* class C4
irb(main):002:0> end
=> nil
irb(main):003:0> c4 = C4.new
=> #<C4:0x00007fcac4188be8>
irb(main):004:0> c4.class
=> C4
irb(main):005:0> c4.singleton_class
=> #<Class:#<C4:0x00007fcac4188be8>>
irb(main):006:0> c4.singleton_class.class
=> Class

c4オブジェクトのクラスはC4クラスだが、それとは別の特異クラスが作成されている。

class_eval

Module#class_eval (Ruby 3.0.0 リファレンスマニュアル)を見ると

モジュールのコンテキストで文字列 expr またはモジュール自身をブロックパラメータとするブロックを評価してその結果を返します。

とある。

渡されたブロックをクラス定義やモジュール定義と同じように扱える、とざっくり解釈した。

例えば先ほどのC4クラスのc4オブジェクトの特異クラスにメソッドを生やすことができる。

irb(main):001:1* class C4
irb(main):002:0> end
=> nil
irb(main):003:0> c4 = C4.new
=> #<C4:0x00007fa8159ac5d0>
irb(main):004:0> c4.singleton_class
=> #<Class:#<C4:0x00007fa8159ac5d0>>
irb(main):005:1* c4.singleton_class.class_eval do
irb(main):006:2*   def hoge
irb(main):007:2*     p "インスタンスメソッド"
irb(main):008:1*   end
irb(main):009:1*
irb(main):010:2*   def self.fuga
irb(main):011:2*     p "クラスメソッド"
irb(main):012:1*   end
irb(main):013:0> end
=> :fuga
irb(main):014:0> c4.hoge
"インスタンスメソッド"
=> "インスタンスメソッド"
irb(main):015:0> c4.singleton_class.fuga
"クラスメソッド"
=> "クラスメソッド"

この場合、hogeメソッドはc4オブジェクトに後から独自のメソッドを生やしているので、c4オブジェクトの特異クラスのインスタンメソッドであり、c4オブジェクトの特異メソッドだと解釈した。

特異メソッド

特異メソッドについて理解が浅かったので調べた。

特異メソッドはある特定のオブジェクトだけで使うことができるメソッドのこと。 一般的な定義は下記のようになる。

irb(main):001:1* class C4
irb(main):002:0> end
=> nil
irb(main):003:0> c4 = C4.new
=> #<C4:0x00007fa8159ac5d0>
irb(main):004:1* def c4.hoge
irb(main):005:1*   "特異メソッド"
irb(main):006:0> end
=> :hoge
irb(main):007:0> c4.hoge
=> "特異メソッド"

先ほどの例のようにc4.singleton_class.class_evalのブロック内でも同様に特異メソッドを生やすこともできる。

参考