meta-programming-ruby
Table of Contents
1 イントロダクション
1.1 Ruby のメタプログラミングの例
- 外部システムに接続する Rubyプログラムで, あらゆるメソッド呼び出しを受付, それを外部システムに送ることのできるラッパが書ける
- DSLを Ruby を拡張することで実現する
- Java プログラマには想像もできないレベルで,コードの重複の排除が可能
- どんなクラスにも自分の欲しいメソッドが追加でき
- 監視したいメソッドの始めと終わりにロギング機能を追加できる
- 好きなクラスをすきな時に継承できる
1.2 頭文字 M
1.2.1 メタプログラミングとは
コードを記述するコードを記述すること
- コードジェネレータとコンパイラ
コードジェネレータとコンパイラは静的メタプログラミング
Rubyでは((* 動的メタプログラミングが可能 *))
- 実行時に自分自身を操作するメタプログラミング
1.2.2 ゴーストタウンと市場
ソースコードを活気ある住民であふれた世界と考える
そこには ((* 変数 )),(( クラス )),(( メソッド *)) が住む それらを言語要素と呼ぼう
多くのプログラミング言語で,言語要素は,肉体のないゴーストだ
ソースコードに居るときは目に見えるが, 実行時には姿が消える.まるでゴーストタウンだ.
- C++では実行時にインスタンスメソッドについて質問することはできない
Rubyでは,実行時もにぎやかな市場だ
1.2.3 メタプログラマ ボブの物語
- ボブの最初の試み
introduction/orm.rb オブジェクト関係マッピングを行うコード
- メタプログラミングに突入
1.2.4 頭文字 M セカンドステージ
メタプログラミングとは,言語要素を実行時に操作するコードを記述す ること ActiveRecord::Base を継承するだけで, 実行時にアクセッサメソッドが定義できる
1.2.5 メタプログラミングと Ruby
Ruby は現在,もっともメタプログラミングに適した言語
2 オブジェクトモデル
2.1 オープンクラス
クラスは定数で,クラス定数で指定でき,変更に対して開かれている.
クラスへの (拡張) メソッドをどこにおくか
- (({class String}))と書けば,既存の String クラスが開かれ,変更可 能.
- (({calss MyString < String})) と書けば,独自の String クラスの拡張 が作れる.
- (({def s.method … end})) と書けば,あるオブジェクトだけが拡張で きる
2.2 クラス定義
(({class})) はそのクラスのスコープを開き,(({end}))までの間のプログラムを そのスコープ内で,実行していく
既存のメソッドを(無意識に)変更することもできる. 無意識は問題だが,意識的な場合は便利なこときまわりない.
2.3 クラスの真実
2.3.1 オブジェクトの中身
- インスタンス変数はオブジェクトにあり,
- メソッドはクラスにある
- オブジェクトに所属するメソッドは,特異(メソッド)
2.4 クラス再び
「(({class C}))定義されるクラスCもオブジェクト」 CはClassクラスを継承
o.class, o.instancemethods, C.superclass,
Classは,(({new()})), (({allocate()})), (({superclass()})) が追加 された Module
2.5 定数
モジュール構造の入れ子構造が定数のスコープ. (({module MyModule MyConstant = 1 class MyClass MyConstant = 2 end end})) 二つの定数のパス ::MyModule::MyConstant ::MyModule::MyClass::MyConstant
定数をまとめるだけのもじゅーるのことを((ネームスペース))と呼ぶ
mod.rbには変数やクラスの定義がある. load('mod.rb')を実行すると,実行後変数は消えるが,定数は残る. load('mod.rb', true) とすると,無名モジュールを作成し, そのスコープで mod.rb を実行する.定数も破棄される
2.6 オブジェクトとクラスのまとめ
オブジェクトとは何か?
- インスタンス変数の集り + クラスへのリンク
クラスとは何か?
- Classクラスのインスタンス + インスタンスメソッド一覧 + スーパーク ラスへのリンク
- ClassクラスはModuleクラスのサブクラス
- つまりクラスはモジュール
3 メソッド
静的言語と動的言語,rubyは動的.
3.1 重複問題
3.1.1 レガシーシステム DS
def get_#{objects}_#{properties}(id) の集り
3.1.2 ダブル,トリプル,トラブル
よく似たコードの羅列
3.2 動的メソッドによる重複コードの排除
Object#send() によるメソッド呼び出し
- obj.send(:mymethod, 3)
メソッド名に対応するシンボル値
動的ディスパッチという
methods/computer/send.rb methods/computer/dynamic.rb methods/computer/moredynamic.rb
3.3 methodmissing()
3.3.1 ghost method
- openstruct
(({ require 'ostruct' icecream = OpenStruct.new icecream.flavor = "ストロベリー" icecream.flavor }))
属性メソッドがゴーストメソッド
3.3.2 動的プロキシ
- ゴーストメソッドは,ラッパーでよく使われる
- メソッド呼び出しをmethodmissing()に集中させる ラップしたオブジェクトに投げる
- Flicrの例
(({ require 'flickr' flickr = Flickr.new(YOURAPIKEY) xml = flickr.tagsgetListUser('userid' => '59542755@N00') tags = xml['who']['tags']['tag'] tags.grep rails })) (({ class Flickr def requrest(method, *params) response = XmlSImple.xmlin(httpget(requesturl(method, params)), {'ForceArray'> false}) raise response['err']['msg'] if response['stat'] !
'ok' response enddef methodmissing(methodid, *params) request(methodid2name.gsub(_, '.'), params1) end … }))
- 委譲 ~/COMM/Prog/ruby/meta/methods/delegator.rb
- DelegateClass() は,ミミックメソッド
未定義のメソッド呼び出しを, 与えられたクラス(のオブジェクト)に委譲する クラスを返す
- リファクタリングするぜ
~/COMM/Prog/ruby/meta/methods/proxy.rb
mycomputer = Computer.new(42, DS.new) mycomputer.cpu
- resopondto?()のオーバーライド
(({mouse(}))) は本物のメソッドではない.
- ドキュメントには現れない
- (({Object#methods}))にも登場しない
- (({Computer}))クラスにゴーストメソッドはあるかと聞いても嘘を つく (({ cmp = Computer.new(0, DS.new) cmp.respondto?(:mouse) }))
- (({respondto?()}))のオーバーライド
~/COMM/Prog/ruby/meta/methods/proxy-extended.rb
superを呼び出すのは他のメソッドの面倒を見てもらうため
- (({Object#methods()}))もオーバーライド?
?
- DelegateClass() は,ミミックメソッド
- リファクタリングのまとめ
- 動的メソッドと動的ディスパッチ DSのラッパーとして,イントロスペクションを使う
- レガシーシステムに委譲
- constmissing()
Module#constmissing() 存在しない定数を参照したとき呼ばれる 任意のネームスペースに定義できる(({ def Object.constmissing(name) name.tos.downcase.gsub(_, ' ') end }))
3.4 クイズ: バグ退治
3.5 もっと methodmissing()
3.5.1 メソッド名が衝突したら
(({ mycomputer = Computer.new(42, DS.new) mycomputer.display # -> nil })) (({Computer#display()})) (({Object.instancemethods.grep d}))
Object#displayがみつかるため,methodmissing にならない
動的プロキシでも同じ問題が起こる
ゴーストメソッド名と継承メソッド名の衝突が原因
継承メソッドを消す
- (({Module#undefmethod()})) は全てのメソッドを消す
- (({Module#removemethod()})) レシーバのメソッドのみ削除
- Builderの例
builderexample.rbFootnotes:
1 FOOTNOTE DEFINITION NOT FOUND: 0