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


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


🐮 メタプログラミングとは

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

🍮 2章

オープンクラス

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

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

class Numeric
def to_money(currency = nil)
Monetize.from_numeric(self, currency || Money.default_currency)
end
end

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

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

Classの親クラスはモジュール

Class.superclass #=> Module

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

loadとrequireの違い

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

モジュールの継承関係

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

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などのモジュールも継承関係に含まれていることが分かる。

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

Rubyにおけるprivateについて

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について

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

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

😼 Tips: トップレベル

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

🐯 refinementsの使いドコロの難しさ

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

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

🐡 参考リンク

📚 おすすめの書籍