DSL
ダイナミックレセプション †
一言要約 †
- 「ダイナミックレセプション」とは、動的にメソッドを生成する技術。
- たとえば、Ruby on Rails の Active Record で使用されている。
要約1 †
- 「ダイナミックレセプション」とは、動的にメソッドを生成する技術。
- 静的言語の場合、定義されていないメソッドを呼び出そうとすると、コンパイル時にエラーになる。
- 動的言語の場合、通常、実行時にエラーとなるが、ダイナミックレセプションでその振る舞いを調整し、特定の動きをするように出来る。
仕組み
- 未定義メソッドを呼び出そうとすると、Ruby の場合 method_missing というメソッドが呼びされるが、それをオーバーライドすることで、その振る舞いを変更できる。
- ダイナミックレセプションを使った良い例のひとつは、自動デリゲートである。
- パラメータの名前を示すものを、メソッド名に埋め込むのも良いやり方。(下記、例)
find_by_firstname("martin")
find_by_firstname_and_lastname("martin", "fowler")
- もうひとつは、Method Chaining と Expression Builder を組み合わせるやり方。(下記、例)
find_by.firstname("martin").and.lastname("fowler)
find_by.firstname.martin.and.lastname.fowler
- 引用符にくるまないようにすると、雑音が減る。
使用するとき
- パラメータをメソッド名に含めると、いくつかの点で魅力的。
- 少ない努力でできる。
- find_by_firstname_and_lastname メソッドが、person クラスにあるのは妥当。
- 多くの組み合わせがあるなら、かなりの時間節約ができる。
- 他の方法(下記)もあるが、流暢ではない。
キーワードとパラメータ find(:firstname, "martin", :lastname, "fowler")
クロージャ find {|p| p.firstname == "martin" and p.lastname = "fowler"}
文字列中に外部DSL find("firstname == martin lastname == fowler")
- これらのことが、特別なケースなしで一般化できるのはダイナミックレセプションだけ。
- ダイナミックメソッドから他の目的に必要であるメソッドまで明確に変換できるときだけ価値がある。
- ダイナミックレセプションの特定のケースを扱うための特別なメソッドを書く必要があるなら、他の方法を使う必要がある。
メソッド名のパーズを使用したプロモーションポイント (Ruby)
- 旅程を旅行するためのポイントを割り当てるスキームを考えてみる。
- フライト、ホテルの滞在、レンタカーなどの項目から成る。
- 人々が頻繁な旅行ポイントを得るのをフレキシブルな方法でできるようにしたい。
- たとえば、ボストンからのフライトに300ポイントを得るとしたら。
@builder = PromotionBuilder.new
@builder.score(300).when_from("BOS")
- 特定の空港から飛んで特定のホテルのブランドに滞在することにより得点する。
@builder = PromotionBuilder.new
@builder.score(350).when_from("BOS")
@builder.score(100).when_brand("hyatt")
- 特定のエアラインでボストンから出発することで得点する複合した飛行ルール。
@builder = PromotionBuilder.new
@builder.score(140).when_from_and_airline("BOS","NW")
- このアプローチは、当然、Active Recordのdynamic finderに似ている。
- もし、興味があれば、Jamis Buck の説明を見てほしい。
要約2 †
- 例:プロモーションポイントのチェーン版(Ruby)
- 上記の例と同じものをメソッドのチェーンを用いて実現する。
- コードイメージは以下の通り
@builder.score(300).when.from.equals.BOS
- モデルは上記の例と同様
- 違いはビルダに現れる
- 式の要素を名前、演算子、条件に分けた上で、各部分を細かく解析する。
- 値をメソッドとして記述するので、ダイナミックレセプションを使う。
class ConditionAtributeNameBuilder...
def method_missing method_id, *args
@content = method_id.to_s
return ConditionOperatorBuilder.new(@parent)
end
- 例:秘密のパネルコントローラから「:」を取り除く(JRuby)
- オリジナル(抜粋)
event :doorClosed, "D1CL"
event :drawOpened, "D2OP"
event :lightOn, "L1ON"
event :doorOpened, "D1OP"
event :panelClosed, "PNCL"
- 取り除いた版(抜粋)
events do
doorClosed "D1CL"
drawOpened "D2OP"
lightOn "L1ON"
doorOpened "D1OP"
panelClosed "PNCL"
end
- イベントの内容をブロックとして受け取り、各イベント名をメソッドとしてダイナミックレセプションで評価する。
class EventBuilder < Builder
def method_missing name, *args
@parent.add_event(name, args[0])
end
end
ファウラーへのフィードバック †
- Typo : dynamically ⇒ dynmacially, dynmically
担当者のつぶやき †
- 要は、DSLにおける可変部分をメソッドとして記述し、存在しないメソッドに対するふるまいをオーバーライドすることでその値を評価すると言う仕組み。「さすがに悪ノリでは?」という気がしてなりません。動的言語はホントになんでもありですね・・・。(わ)
- 自分の中では自然言語を読む脳とプログラム言語を読む脳は違うような気がしています。プログラム言語を自然言語に近づけ過ぎると逆に何をやっているか分からない。関数型言語に詳しくなると、もう少し考え方が変わるんでしょうか?(わ)
みんなの突っ込み †