メタプログラミングRuby第2版:2章:オブジェクトモデル

スポンサーリンク

メタプログラミングRubyとは?

出版社である O’Reilly Japanのサイトからの引用です。

本書はRubyを使ったメタプログラミングについて解説する書籍です。メタプログラミングとは、プログラミングコードを記述するコードを記述することを意味します。前半では、メタプログラミングの背景にある基本的な考えを紹介しながら、動的ディスパッチ、ゴーストメソッド、フラットスコープといったさまざまな「魔術」を紹介します。後半では、ケーススタディとしてRailsを使ったメタプログラミングの実例を紹介します。今回の改訂では、Ruby 2とRails 4に対応し、ほぼすべての内容を刷新。Rubyを使ったメタプログラミングの魔術をマスターし、自由自在にプログラミングをしたい開発者必携の一冊です。

引用:https://www.oreilly.co.jp/books/9784873117430/

2章:オブジェクトモデルを写経し終えたので内容を振り返ってまとめてみました。

オープンクラス

標準クラスや定義済の既存クラスは再オープンしてメソッドを追加することができる。
この技法をオープンクラスと呼ぶ。

class String
  def to_alphanumeric
    gsub(/[^\w\s]/, '')
  end
end

'#3, the *Magic, number*?'.to_alphanumeric
# => "3 the Magic number"

しかし、安易なメソッド名を定義すると既存メソッドと重複して内容を上書いてしまい、バグを引き起こす。

class Array
  def replace(original, replacement)
    self.map { |e| e == original ? replacement : e }
  end
end

これはArray#replaceを上書いてしまうので、他で用いていた場合はバグを引き起こす。

このような安易なパッチをモンキーパッチという蔑称で呼ぶこともある。
利用時には既存メソッドを調べること。

オブジェクトモデルの内部

インスタンス変数

  • オブジェクト内でメソッドが呼ばれて実際に値が代入されて初めて生成される
  • クラス内の定義段階では生成されていない
  • つまり、インスタンス変数はオブジェクトに住んでいる
class MyClass
  def my_method
    @v = 1
  end
end

obj = MyClass.new
puts obj.instance_variables # => []
obj.my_method
puts obj.instance_variables # => [:@v]

メソッド

  • メソッドはクラスに住んでいる
  • しかし、実際にそのメソッドを持っているのはクラスから作られたインスタンス
  • オブジェクトのメソッド = クラスのインスタンスメソッド

クラス

すべてのクラスはClassクラスのインスタンスである。

  Class.class # => Class
  Class.superclass # => Module
  Module.superclass # => Object
  Object.superclass # => BasicObject
  BasicObject.superclass # => nil
  Module.class # => Class
  Object.class # => Class
  BasicObject.class # => Class

モジュール

クラスとはオブジェクトの生成やクラスを継承するための3つのインスタンスメソッドを追加したモジュールである。

  Class.instance_methods(false) # => [:allocate, :new, :superclass]
  Class.superclass # => Module

メソッドを呼び出すときに何が起きているの?

メソッド探索

  • メソッドが呼ばれると、レシーバのクラスに入り、メソッドを見つけるまで継承チェーンを登る
  • 継承チェーンとは、superclassメソッドで辿ることができるルート
  • 継承チェーンにはModuleも含まれる
  • include, prependではモジュールが継承チェーンに組み込まれる場所が変わる
module M1
  def my_method
    'M1#my_method()'
  end
end

class C
  include M1

  def my_method
    'C#my_method()'
  end
end

class D
  prepend M1

  def my_method
    'D#my_method()'
  end
end

c = C.new
p C.ancestors # => [C, M1, Object, Kernel, BasicObject]
puts c.my_method # => C#my_method()

d = D.new
p D.ancestors # => [M1, D, Object, Kernel, BasicObject]
puts d.my_method # => M1#my_method()

モジュールが多重インクルードされた場合、2回目以降の挿入は無視される。

module M1
end

module M2
  include M1
end

module M3
  prepend M1
  include M2
end

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

Kernel

  • Objectクラスがインクルードしているモジュール
  • printなどが定義されている
Kernel.private_instance_methods.grep(/^pr/)
  #  => [:proc, :printf, :print]

selfキーワード

レシーバを明示していないメソッドのレシーバはselfになる。

class MyClass
  def testing_self
    @var = 10
    my_method # => self.my_method と同じ
    self
  end

  def my_method
    @var = @var + 1
  end
end

obj = MyClass.new
puts  obj.testing_self.inspect
# => #<MyClass:0x00007fadb118ca70 @var=11>

Refinements

モンキーパッチにならないようなオープンクラスにするために用いる。

モジュール内部で refine を用いて定義する。

使い方①:トップレベルで using モジュール名 を呼び出した後のソースファイルで有効

module StringExtensions
  refine String do
    def to_alphanumeric
      gsub(/[^\w\s]/, '')
    end
  end
end

using StringExtensions

puts '#3, the *Magic, number*?'.to_alphanumeric
# => 3 the Magic number

使い方②:モジュール内で using モジュール名 を呼び出した後、endまで有効

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

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

puts 'my_string'.reverse # => gnirts_ym
この記事の内容が役に立ったと思いました、SNSで記事を共有していただけますと幸いです。