LoginSignup
0
0

More than 3 years have passed since last update.

メタプログラミングRuby 2章 完全理解した

Last updated at Posted at 2019-12-11

Overview of p29-p45

途中 → 途中まで読んだので、メモしておきます。
全部メモしたら追記しておきます。

メソッド探索

Ruby がレシーバのクラスに入り、メソッドを見つけるまで継承チェーンを上ることをメソッド探索と呼ぶ。

例えば下記のように、レシーバである MySubClass のスーパークラスを辿っていくことで、 my_method を探し出すことが出来る。

class MyClass
  def my_method; 'my_method()'; end
end 

class MySubClass < MyClass
end

obj = MySubClass.new
obj.my_method() # => "my_method()"

継承チェーンとは BasicObject までのクラスの継承関係の軌跡のことで、Module#ancestors で参照することができる。

MySubClass.ancestors # => => [MySubClass, MyClass, Object, Kernel, BasicObject]

モジュールとメソッド探索

Ruby はモジュールを継承チェーンに挿入する。モジュールは2通りの挿入方法があり、1つはインクルード、2つめはプリペンドだ。

インクルードはクラスの真上にモジュールを挿入し、プリペンドはクラスの真下にモジュールを挿入する。

module M1
  def my_method
    'M1#my_method()'
  end
end

class C
  include M1
end

class D < C; end

D.ancestors # => [D, C, M1, Object, Kernel, BasicObject]

class C2
  prepend M1
end

class D2 < C2; end

D2.ancestors # => [D2, M1, C2, Object, Kernel, BasicObject]

多重インクルード

あるモジュールが既に継承チェーンに含まれているとき、2回目以降の挿入は無視される。

下記の例では、M1 は既に M3 の継承チェーンに含まれているため、 M2 でインクルードされている M1 は無視される。

module M1; end

module M2
  include M1
end

module M3
  prepend M1
  include M2
end

M3.ancestors # => [M1, M3, M2]

Kernel

Object クラスが Kernel モジュールをインクルードしているため、Kernel モジュールのメソッドはどこからでも呼び出すことが出来る。

下記から分かるように、print は Kernelの private インスタンスメソッドであるため、どこからでも呼び出せる。

Kernel.private_instance_methods.grep(/^pr/) # => [:proc, :printf, :print]

メソッドの実行

self キーワード

メソッドを呼び出すとき、メソッドのレシーバが self になる。

全てのインスタンス変数は self のインスタンス変数になり、レシーバを明示しないメソッド呼び出しは全て self に対する呼び出しとなる。

class MyClass
  def testing_self
    @var = 10
    my_method
    self
  end

  def my_method
    @var = @var + 1
  end
end

obj = MyClass.new
obj.testing_self # => #<MyClass:0x00007feae89f4dd8 @var=11>

self の特殊ケース

  • トップレベル
    • メソッドを呼び出さないとき、self は Ruby のインプリンタが作った main 内部にいる。
    • このオブジェクトはトップレベルコンテキストと呼ばれる。
  • クラス定義と self
    • クラスやモジュールの定義の内側では、self の役割はクラスやモジュールそのものになる。

private の本当の意味

  • Ruby では「private ルール」が定義されている。
    • ルール1: 自分以外のオブジェクトのメソッドを呼び出すには、レシーバを明示的に指定する必要がある
    • ルール2: private のついたメソッドを呼び出すときはレシーバを指定できない

以下の例では、ルール2 を破っているためエラーとなる。

class C
  def public_method
    self.private_method
  end

  private

  def private_method; end
end

C.new.public_method # => NoMethodError (private method `private_method' called for #<C:0x00007feae8a3c7a0>)

Refinements

クラスを再定義して拡張する機能として Refinements がある。
Refinements はモンキーパッチと似ているが、影響範囲が特定のスコープに限定される点で異なる。

以下の例では、refine ブロック内で拡張された reverse メソッドを、using メソッドを使うことで有効にしている。

module StringExtensions
  refine String do
    def reverse
      "esrever"
    end
  end
end

module StringStuff
  using StringExtensions
  "my_string".reverse # => "esrever"
end

"my_string".reverse # => "gnirts_ym"

Refinements は現在も進化を続けている機能であるため、ベストプラクティスは確立されていないようです。

まとめ

  • クラスはそれぞれ BasicObject まで続く継承チェーンを持っている。
  • クラスにモジュールをインクルード(プリペンド)すると、そのクラスの継承チェーンの真上(真下)にモジュールが挿入される。
  • メソッドを呼び出すと、レシーバが self になる。
  • モジュール(あるいはクラス)を定義すると、そのモジュールが self になる。
  • インスタンス変数は常に self のインスタンス変数と見なされる。
  • レシーバを明示的に指定せずにメソッドを呼び出すと、self のメソッドだと見なされる。
  • Refinements は using を呼び出したところから、ファイルやモジュールの定義が終わるところまでの限られた部分でのみ有効になる。

所感

  • メタプロ、面白い。
  • なんで面白いかというと、曖昧だったオブジェクトとクラスの違いが整理されている気がしている。
    • 今までは「鯛焼きの型がクラスで、出来あがった鯛焼きがオブジェクトだよ」みたいな抽象的な理解に落ち着いてた。
    • でも、実際はインスタンス変数やメソッドがどちらに帰属するのかなど、言語レベルで明確な違いがある。
  • メタプロを読んだ後にプロRuby のクラス・モジュール以降を読むともっと理解深まるかもしれない。
0
0
0

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
0
0