酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

メタプログラミング Ruby 第2版 / 第2章 オブジェクトモデル [勉強メモ]

メタプログラミングRubyを読んでいます。完全自分用のメモ記事です。 第2章で特に参考になった部分を中心に書いています。


メタプログラミングとは

コードを記述するためのコードを書くことである

2章

オープンクラス

1
2
3
4
require "monetize"

standard_price = 100.to_money('USD')
standard_price.format #=> $100.00

とある場合、 to_money は以下のように Numeric クラスを拡張して実装されている。

1
2
3
4
5
class Numeric
  def to_money(currency = nil)
    Monetize.from_numeric(self, currency || Money.default_currency)
  end
end

Rubyは標準クラスでも気軽に拡張できる。 既存のクラスを再オープンして、気軽に拡張できる。これをオープンクラスという。 安易なパッチはバグを誘発するため、モンキーパッチともいう。

しかし例えば拡張したメソッドが標準クラスのメソッドだった場合、その影響範囲が大きくなってしまう。 モンキーパッチの代替案としては、Refinements などを使うことである。

Classの親クラスは Module

1
Class.superclass #=> Module

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

load と require の違い

- load => loadはコードを実行するために使う。呼び出す度にファイルを実行する
- require => requireはライブラリをインポートするために使う。ファイルは一度しか読み込まない。

モジュールの継承関係

継承チェーンはクラスからスーパークラスに向かって進む。 それだけではなく、継承チェーンにはモジュールも含まれる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module M1
  def my_method
    'M1#my_method()'
  end
end

module M2
  def my_method_second
    'M2#my_method_second()'
  end
end

class C
  include M1
  prepend M2
end

class D < C; end

# 継承関係を見るメソッド
puts D.ancestors.to_s # => [D, M2, C, M1, Object, Kernel, BasicObject]

このようにM1やKernelなどのModuleも継承関係に含まれている事がわかる。

ちなみに、モジュールがすでにチェーンに存在していた場合は、2回目の挿入を無視する。 (何度も継承チェーンに同じオブジェクトが含まれることはない)

Rubyにおける private について

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class C
  def public_method_no_self
    private_method
  end

  def public_method_with_self
    self.private_method
  end

  private

  def private_method
    'called private method'
  end
end

puts C.new.public_method_no_self

puts C.new.public_method_with_self

Refinementsについて

module で限定されたスコープの中でのみ、Stringを限定して拡張できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module StringExtensions
  refine String do
    def reverse
      'esrever'
    end
  end
end

module Strings
  using StringExtensions

  puts 'my_string'.reverse #=> esrever
end

puts 'my_string'.reverse #=> gnirts_ym

これにより、モンキーパッチで発生するようなグローバルな変更を避けることができる。 ただし、Refinementsは新しい機能で、将来挙動が変わる可能性がある。 そのことを理解したうえで、プログラムを書くべきである。

まとめ

  • オブジェクトは複数のインスタンス変数とクラスへのリンクで構成
  • インスタンス目ドッドはオブジェクトの Class に住んでいる
  • クラスは Class クラスのオブジェクトである。クラスは単なる定数である
  • Class は Module のサブクラスである
  • Module は基本的にはメソッドをまとめたものである
  • クラスは new でインスタンス化したり、 superclass で階層構造を作ったりできる
  • クラスはそれぞれ、BasicObject まで続く継承チェーンを持っている
  • クラスにモジュールをinclude(prepend)すると、そのクラスの継承チェーンの真上(下)にモジュールが挿入される
  • メソッドを呼び出すときには、レシーバがselfになる
  • モジュール(あるいはクラス)を定義するときには、そのモジュールがselfになる
  • インスタンス変数は常にselfのインスタンス変数とみなされる

Tips: トップレベル

1
2
self #=> main トップレベルでのselfを表すオブジェクト
self.class #=> Object

refinementsの使いドコロの難しさ

refinementsは便利だけど、メソッドの一覧で取得できない場合があり、直感的ではないのでいずれこの仕様は変わる可能性がありそう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class MyClass
  def my_method
    'original method'
  end

  def fuga
    "fuga"
  end
end

module MyClassExtensions
  refine MyClass do
    def my_method
    'refinement method'
    end

    def hoge
      'hogehoge'
    end
  end
end

module Foo
  using MyClassExtensions
  puts (MyClass.new.methods - Object.instance_methods).to_s #=> :hoge がメソッドにない
end

サンプルソース

何かの役に立つこともあるかもなので、リポジトリも公開しておきます。

morizyun/meta_programming_ruby2 - GitHub


Special Thanks

おすすめの書籍