メタプログラミングRubyを読んだ
メタプログラミングRuby 第2版は、初版のほうは軽く読んだことがあったが第二版のほうはまだ読んでなかったので読んでみた。
内容はオープンクラスや動的メソッド定義とか、いわゆるRubyの黒魔術やメタプログラミングに関する話。
最初に読んだときはたしか2013年くらいで、初めてのRuby -> Rubyレシピブック 第3版 303の技 とか読んでRails tutorialやってますみたいな感じだった気がする。なのでRubyに関しては完全に初心者でメタプログラミングRubyは難しくて正直ピンとこない部分が多かった。
その後、仕事でRubyとRailsをメインで使うようになり、Railsやgemの内部構造でハマったりしてソースを読むたびにつまみ食いな感じでmethod_missingやdefine_methodとかについて調べたりしてた。
なので本書で扱われるトピックについてはある程度知っていることもたしかにあったけど、改めて基礎的なObjectとClassの関係から継承ツリーの話・define_methodやmethod_missingの話・XXXX_eval系の話という風により高度な話に展開していきながら体系的に再学習できたのは良い体験だった。
こういう少し発展的な内容は実際の経験を積んだ上で読み返すと「あ〜あのときのあれね、あ〜」という納得感が頻発するので理解が進む気がする。
ちなみに一応下記のコードを読んで、
# attr_accessorにバリデーションを付加したクラスメソッドを生やす例 module CheckedAttributes def self.included(base) base.extend ClassMethods end module ClassMethods def attr_checked(attribute, &validation) define_method "#{attribute}=" do |value| raise "#{value} is invalid value" unless validation.call(value) instance_variable_set("@#{attribute}", value) end define_method attribute do instance_variable_get("@#{attribute}") end end end end class Person include CheckedAttributes attr_checked :age do |v| v > 18 end end person = Person.new person.age = 39 # => OK person.age = 16 # => 例外
- self.includedはただのincludedではなく、なぜself.includedなのか
- なぜClassMethods内で定義しないといけないのか
- attr_checkedは継承ツリーにおいてどこに定義されるのか
- &validationの挙動
- define_methodの挙動
- instance_variable_setの挙動
とかをちゃんと説明できるなら本書で学ぶことの8割くらいは理解できてる気がする。そういう人は、付録の魔術書一覧(メタプログラミング関連技法のクイックリファレンス)をざーっと読んで、あとはお茶でも飲みながらボブとビルの愉快なやりとりでもさら〜っと読んだら十分だと思う。
あと第6章の上司がボブに設計に関してお願いをするシーンが生々しくてよかった。
『彼女は往年のプログラマで、今でも自分でプログラムをすることがあるが、それが周りの迷惑になることもある。あなたは新人なので、上司の依頼を断ることはできない。』 (メタプログラミングRuby 第2版 p145)
本書はこのようにボブとビルとその周りの人々のやりとりをベースに話が進むのでたしかにAmazonのレビューにもある通りやや冗長になりがちではあるけれども、それでも話として結構面白い気もするし、何よりこれでも第1版より冗長なストーリーを減らしてるらしいので頑張って読みましょう。
あとは雑な読書メモ。
メタプログラミングRuby
モチベーション
1章・2章
- ObjectとClassの関係の話。
- 最後のこの章のまとめに箇条書きで大事なこと書いてるしそれ読めばよい感じ。
オープンクラス
- 定義したクラスに再度メソッド追加したりできる性質
- 既存のメソッドをオーバーライドするとやばい
- 昔から動かしてるRailsアプリとかはlib/monkey_patches/配下にオープンクラスで定義してるクラスファイルとか結構ある印象
loadとrequire
- requireはライブラリとかimportする。同じファイルを二度実行しない。
- loadはファイルをなんども読み込める。
多重インクルード
- 継承チェーンの2回目のinclude,prependは無視される
refinements
using
したclassとかmodule内でのみ適用されるネームスペースを設けるみたいな感じ。メソッド探索のモンキーパッチ。- 2.0から入ったのは知ってたけど実際あんま使ったことないから復習になった
- #refine
継承ツリー
- Object > Class > MyClass > obj みたいなやつ。moduleをinclude,prependしたらこのツリーのどこにオブジェクトが積まれるか意識しとけば大体ok。
self
- 明示的にレシーバが設定されてるとき以外の大体のレシーバがこいつ
3章
動的メソッド
- send :method_name, *args
- レシーバに:method_nameを呼び出せる
動的ディスパッチ
- 実行時にメソッドを呼び出せる
- define_method :method_name, &block
動的メソッド
- 実行時にメソッドを定義できる
ゴーストメソッド
- method_missingを使うやつ
- respond_to_missing?
- const_missing
- method_missingの無限ループ問題
- method_missingにたどり着かない問題
- 既存のオブジェクトにすでに正規のメソッドが定義されてたときとか
- ブランクスレート
- 必要最低限のメソッドしかないオブジェクト(BasicObject)を継承させて事故を防ぐ
4章
- ブロックの話
- メソッドにブロック渡すとyieldで実行されるぜ的な基本から書いてる
- block == クロージャだよ
- スコープゲート
- scopeが変わるキーワード
- module class def
- フラットスコープ
- classやdefのスコープを超えられる
- Module.new + block, Class.new + block, define_method + block
- instance_eval
- レシーバのオブジェクトをこじ開けて改変できるあれの話
- instance_exec(arg) {} は知らなかった。instance_evalに引数渡せるやつ。こんなん使う場面あるの?
- 呼び出し可能オブジェクト
- 評価ができてスコープを持ち運べる
- 遅延評価をあとで評価と訳してて微妙
- Proc | lambda | ->(){}
- ブロックをオブジェクトにしたもの(ブロックはそもそもオブジェクトではない)
- Procとlambdaはreturnの挙動が違う
- p = Proc.new {return 10}はスコープから抜ける
- l = lambda {return 10}は単純にl に10が返る
- UnboundMethod
- メソッドを持ち出して呼び出し可能オブジェクトにしたやつ
- MyModule.instance_method(:my_method)みたいな感じで取れる
DSL作る話
- &blockを渡したり、lambda + send + define_methodで変数をlambdaのスコープに閉じ込めてObjectに定義したりとか必要最低限かついろんな要素を使ってDSLの定義の仕方が学べてお得っぽい章
5章 クラス定義
図 5-5 特異クラスと継承(ページ129).
- クラス、スーパークラス、特異クラスのこの辺ちゃんと理解しないで生きてたがこの図で全部理解した
- 特異クラス
- 特異メソッドが生えてる空間
- クラスメソッドは特異クラスの特異メソッド
6章
- Kernel#eval
- コード文字列の評価
- class_eval + define_method + instance_valuable_set + instance_valueable_getでevalを使った動的にクラス生成するやつを書き換えられる。かなり破茶滅茶な感がある。
- フックメソッド
- めっちゃいっぱいある。includedしか使った事無い
- Module, Class, 継承時のフック
- included, prepended, inherited
- メソッドのフック
- method_added, method_removed,method_undefined
- 特異メソッドのフック
- singleton_method_added, singleton_method_removed,singleton_method_undefined
Ⅱ Railsのメタプログラミング話
- ActiveRecordの話
- ActiveSupport::Concernの話
- モジュールをinlcudeしてクラスメソッドとインスタンスメソッドの両方が手に入る理由
- ActiveSupport::ConcernがModule#append_features(includeの実態)をオーバーライドして依存オブジェクトのincludeとextendを行ってるから
付録
- 付録の魔術書がかっこいい。本書で登場したメタプログラミングの手法が簡潔な説明とコードでまとまってる。いわゆるクイックリファレンス