DSLとRubyの動的グッズ

この記事では、ドメイン固有言語(DSL)を構築するためのRubyの基本機能を説明します。 DSLは、特定の問題を解決するための非常に特殊な小さな言語です。 C ++やJavaなどの汎用言語とは異なり、DSLは通常、非常にコンパクトであり、解決される問題の文脈において表現力が豊かです。

さまざまなDSLがRubyのライブラリとフレームワークで広く配布されています。 たとえば、Railsでは、DSLを使用して移行を作成します。

それでは、RubyがDSLを構築するために提供する機能を見てみましょう。


コンピューターの構成を記述する単純な形式が必要になります。
簡単な例:
 CPU:2.2 GHz
メモリー:1ギガバイト
ディスク:250ギガバイト

ここで、Rubyの助けを借りて、このような記述に便利なDSLを作成します。

ステージ1。


この記述をRubyコードに変換します。たとえば、次のようにします(メモリをメガバイトで、周波数をメガヘルツで保存します)
 comp = Computer.new
 comp.cpu = 2.2 * 1024
 comp.ram = 2 * 1024
 comp.disk = 1 * 1024

クラスコード基本:
クラスコンピュータ
     attr_accessor:cpu
     attr_accessor:ram
     attr_accessor:ディスク
終わり

Rubyでは、すべてのインスタンス変数(@で始まる変数など)はプライベートです。 オブジェクトメソッド内でのみアクセスできます。 属性を作成するには、この属性の値を設定および取得するための2つのメソッドを宣言する必要があります。
 def cpu
     @cpu
終わり

 def cpu = val
     @cpu = val
終わり

または単に呼び出す: attr_accessor :cpu 、これらのメソッドを生成します

ステージ2。


それで何? ここには新しいものは何もありません。属性を持つオブジェクトを使用するだけです。 コードを少し改善してみましょう。
あなたの目を引く最初のものは、ギガバイトを独立してメガバイトに変換するなどです。 直せ!
 comp = Computer.new
 comp.cpu = 2.2.ghz
 comp.ram = 2.gb
 comp.disk = 1.gb

これを行うには、 ghzメソッドとgbメソッドをNumericクラスにミックスしghz
数値クラス
     def ghz
        自己* 1000
    終わり
     def gb
        自己* 1024
    終わり
     def mhz
        自己
    終わり
     def mb
        自己
    終わり
終わり

また、2つのメソッドmhzとmbを追加しました。cpu.ram= 512の代わりにcpu.ram = 512.mbです。

Rubyには、新しいメソッドを任意のクラスにミックスインする機能があります。 つまり クラスは作成された後でも拡張できます。 メソッドをクラスと混合すると、すべてのインスタンスで使用可能になります。
クラスString
    デフクール
         self +「かっこいい!」
    終わり
終わり

cool selfメソッドでは、これはオブジェクト自体の値へのポインターであり、メソッドの戻り値はその最後の行の実行の結果であるため、
puts "my string".cool
「My string is cool!」と表示されます

メソッドghzなどは、Numericクラスに混合しました。これは、Rubyのすべての数値の親クラスであるためです。 整数と小数の両方について

ステージ3。


すでに良い。 しかし、「comp」を示す必要があることも恥ずかしいです。各パラメーターの前に。 小さなガールフレンドを作りましょう:
 comp = Computer.new do
     CPU 2.2.ghz
    ラム2.gb
    ディスク1.gb
終わり

それははるかに良く見えますよね? しかし、問題は、それが機能するかどうかです。
それを理解しましょう。 有効なRubyコードのようです。 CPU、RAM、およびディスクは、コンピュータークラスのインスタンスで呼び出されないため、メソッドではなく機能になりました。
このようなもの:
 def cpu val
     comp.cpu = val
終わり

しかし、この関数にcomp変数をどのように渡すのでしょうか?
cpu 2.ghz、comp? しかし、その後、すべての表現力が引き出されます。 さて、このオブジェクトのコンテキストでこれらのメソッドを実行できたら...
そして、やっぱりできる! Rubyは、instance_evalメソッドを使用してこの機会を提供します。

次に、Computerクラスの新しい実装を見てください
クラスコンピュータ
     #initializeメソッドはコンストラクターです
     def initialize&block#&blockは、コードブロックがメソッドに渡されることを意味します
         instance_eval&block#マジックのinstance_evalメソッドを呼び出してブロックを渡します
    終わり

     #ここでは、属性の代わりにメソッドを宣言したため、cpu = 2.ghzの代わりにcpu 2.ghzと記述します
     def cpu val
         @cpu_clock = val
    終わり

     def ram val
         @ram_size = val
    終わり

     def disk val
         @disk_size = val
    終わり

     #CPUメソッドなどがあるため、値を設定する必要はありません。
     #したがって、attr_accessorの代わりにattrを呼び出しますが、値を設定するメソッドを生成しません
     #しかし、わずかな制限があります。 動的言語はメソッドをオーバーロードできません
     #(attr_accessorは実際にメソッドを作成します)
     #次に、属性に対して他の名前を選択する必要があります
     attr:cpu_clock
     attr:ram_size
     attr:disk_size
終わり


このメソッドは何をしますか? すべてが非常に簡単です。 すでに上で述べたように、このオブジェクトのコンテキストでブロック(またはコードのある行)を実行します。
また、CPUなどのメソッドはComputerクラスで宣言されているため、呼び出されます。 そして、このオブジェクトのためだけに。

ステージ4。


ここで、コンピューターにさまざまな特性が無制限にあると想像してください。 たとえば、BIOSサイズまたはバス解像度:
 comp = Computer.new do
     BIOS 0.5.mb
    バス100
終わり


誰もが予測することはできませんが、そのような特性を追加できるようにしたいと思います。
そしてここで、Rubyの機能、つまりmethod_missingメソッドが再び役立ちます。
method_missingは、存在しないメソッドを呼び出そうとしたときに呼び出されるオブジェクトの特別なメソッドです。 例:
クラステスト
     def method_missing name、* args、&block #nameはメソッドの名前、* argsはその属性、&blockはコードブロック(存在する場合)です。
         name.to_s + "called"を置きます
    終わり
終わり

 t = Test.new
 t.some_method#「some_method called」を出力します
 t.asdf# "asdf called"


Computerクラスに戻ります。
クラスコンピュータ
     def初期化とブロック
         instance_eval&ブロック
    終わり
    
     def method_missing name、* args、&block
         instance_variable_set( "@#{name}"。to_sym、args [0])#インスタンス変数を作成して値を割り当てる
         self.class.send(:define_method、name、proc {instance_variable_get( "@#{name}")})#この変数にアクセスするためのメソッドを作成
    終わり
終わり


今、それはどのように機能しますか ブロックをクラスのコンストラクターに渡します。 このブロックは、このクラスのインスタンスのコンテキストで実行されます。 実行中に、存在しないメソッドが呼び出されます;代わりに、 method_missingは、nameパラメーターがメソッド名に等しく、 args引数の配列で実行されます。 次に、メソッドに一致する名前と、呼び出されたメソッドの最初の属性に等しい値を持つインスタンス変数を作成します。 この変数の値を取得するメソッドと同様に。

ところで、method_missingはActiveRecordのRailsでPerson.find_all_by_name何千ものメソッドを作成するために使用されます

結果のコードはここにあります

さらなる改善


DSLをさらに便利にするために、他に考えられることはありますか?
DSLはプログラマーだけでなく、プログラミングに不慣れな人々のためにも設計できるため、プログラマーでも編集できないファイルに記述を転送することは論理的です。 そして、このファイルをロードして解釈します。
my_pc.conf:
cpu 1.8.mgh
ram 512.mb
disk 40.gb

これを行うのは非常に簡単です。上で書いたように、instance_evalメソッドにブロックの代わりにコードを含む行を渡すことができます。

第二に、ロシア語のファンの場合、次のように書くことができます。
comp = Computer.new do
2.2.ghz
2.gb
1.gb
end

これが機能するためには、インタプリタを呼び出すときに-Kuオプションを追加するだけです。 たとえば、次のように:
ruby -Ku test.rb

上記では、有効なRubyコードである単純なDSLを構築しました。 ただし、有効性は拒否できます。 たとえば、ポイントを取り除きます。
cpu 1.ghz代わりにcpu 1.ghz記述します。 次に、少し前処理を行う必要があります。 たとえば、正規表現を使用して、これらのポイントを追加します。

そして今、これらの改善点を組み合わせると、最初に挙げた例を解釈できます。
 CPU:2.2 GHz
メモリー:1ギガバイト
ディスク:250ギガバイト

自分で試してみてください:)

今少し自己宣伝:)
この手法を自分で考え出したとき、DSLを使用して有効なXHTMLを生成する簡単なライブラリを作成しました。
ポイントは、無効なものを書き込もうとすると、生成プロセスでエラーが発生することです。
gemパッケージとして設計したので、次のように配置できます。
Windowsの場合: gem install rml
* nixシステムの場合: sudo gem install rml
プロジェクトページ: http : //rubyforge.org/projects/rml/
例とドキュメントは、 http//rml.rubyforge.org/にあります。
真実は英語にありますが、興味があれば、簡単なメモを書くことができます。

Source: https://habr.com/ru/post/J48754/


All Articles