RubyでDSL作成入門、メタプログラミングRubyを読んで

メタプログラミングRubyを何回も読み返していまして、現在5回目を読み終わったところです。さすがに5回も読みますと、Rubyのメタプログラミングな側面の理解がかなり進みました。ということで Ruby での DSL(domain specific language)作成の入門として、簡単な DSL を書いてそれを実行できるように実装してみました。メタプログラミングRubyの第3章あたりの内容です。

スポンサーリンク

DSL の仕様作成

ディレクトリの構造は以下、DSL ファイル自体は tests/bmi_profile.rb に書きました。judge_profile.rb が DSL の処理を行う実装ファイルとなります。

以下のような DSL(内部DSL)を書けるようにします。setup メソッドで、プロフィール用の身長(@height)・体重(@weight)・BMI値(@bmi) を、各々 profile メソッドの実行前にセットアップします。全て setup された値を元に、それぞれ profile メソッド呼び出しのブロックを評価して、ブロックが true を返したものだけを出力するという仕様。

tests/bmi_profile.rb

DSL を読み込んで実行時の出力

上記の DSL ファイルを読み込んで実行させますと、次の出力となるように judge_profile.rb を実装します。

“— set height and weight —“, “— set bmi —” が表示され、各々 profile メソッド呼び出しの前に、毎回全てセットアップされています。そして、profile メソッド呼び出しのブロックの評価が true のものだけ出力。上記 DSL の例ですと、セットアップした値が @height(168)、@weight(58)、@bmi(20.5) ですので、各々 normal を出力しています。

DSL を読み込んで実行させるための実装

では、judge_profile.rb の実装です。DSL ファイル(tests/*profile.rb)を読み込んで、結果を出力させるための実装となります。

judge_profile.rb

あとはこの judge_profile.rb のパーミッションを755にして実行すると、DSL のファイル(tests/*profile.rb)を処理できます。

使ってる技術を少々解説

詳しくは、メタプログラミングRubyを読んでくさい!と言いたいところですけど、少々説明。

まずは、Dir.glob で ./tests/ ディレクトリ内の DSL ファイルを全て読み込んでいます(load file)。load file 時に、judge_profile.rb の define_method で定義した、setup, profile により setups 配列と profiles ハッシュが設定される。

lambda
lambda 内にコードを閉じ込めて call することで、グローバル領域の不要な汚染を防ぐ。

Module#module_eval
DSL の profile, setup メソッドは、レシーバ指定なしで呼べるように Kernel モジュールのメソッドとして定義する。また、lambda 内の最初で初期化した setups 配列、profiles ハッシュにアクセスできるように、Module#module_eval を使う。”module Kernel ~ end” によるモンキーパッチだと、スコープが変わりそれらにアクセスできなくなる。(フラットスコープ/クロージャ)

Module#define_method
Module#define_method は動的にメソッドを定義する手段、それと同時にフラットスコープにより setups, profile にアクセスできる。”def profile ~ end” の書き方だとスコープが変わり、それらにアクセスできなくなる。

&block と block.call
ブロックをメソッドの定義時に引数として受けとるには、&block という風に書きます。block 自体は Proc オブジェクトなので、実行するときには block.call で呼び出す。”block.call entry, profile” とすることで、ブロック引数を渡せます。

class CleanRoom
setup, profile のブロックを評価するためのクリーンルーム用クラス。このオブジェクトで instance_eval することで、このオブジェクトのインスタンス変数として、DSL ファイル内の @height, @weight, @bmi が setup され、profile のブロックが評価される。

BasicObject#instance_eval
レシーバのコンテキストで、ブロックを評価します。

以上です。このくらいの内部 DSL であれば、このように簡単に実装することができます。テストフレームワークの仕組みをある程度理解できました。メタプログラミングを学ぶと、さらなる Ruby の柔軟性と動的なパワーに驚かされますね。

ちなみに、繰り返しメタプログラミングRubyを読んでいるのは、メタプログラミングRubyがそれに値する本であるのと、7回読み勉強法を試してみているのが理由です。初回読んだ時は読了に何日もかかったのですが、5回目は2時間程度で読み終えられたので、どんどん楽になると同時に理解も深くなって良い感じです。

教科書を7回読むだけで、断然トップになれた!(前編):PRESIDENT Online – プレジデント

スポンサーリンク
私は以下の本で Ruby を覚えました。メタプログラミングRubyは入門を超える内容で難しめです。
スポンサーリンク
 
Twitterを使っていますのでフォローお願いたします!ブログの更新情報もつぶやいてます^^
(英語学習用)

Leave Your Message!