1.メタプログラミングパターン-25 kyu。 評価方法

プログラミングは今でも時々行っていますが、徐々にそのスタイルを変えており、メタプログラミングにますます関連しています。 同時に、普通のプログラミングが嫌だとは言えません。 プログラマーと同じように、コードのモジュール性、簡潔性、わかりやすさ、柔軟性をますます高める方法を模索しています。メタプログラミングでは、未開発の可能性があります(Lispからの無限の無限のインターネットメタプログラミングの洪水にもかかわらず)。 :)



Rubyでのメタプログラミングに関するブログを始めたいと思います。



Rubyの選択は、Rubyプログラマーの環境でのメタプログラミングの文化がすでに大部分を形成しており、メタプログラミングの要素がRubyプログラマーの日常作業の構造になっているという事実によるものです。さらに、他の動的言語よりもよく知られています。



私はPhysTechでRuby&Rails&Metaprogrammingに関する講義を行っています。 講義の1つからの資料はここで取ることができます 。 写真には主なものの簡単な要約があります。



このブログでは、一貫した詳細な方法でトピックを紹介しようとします。 タスクは単純ではないため、事前に深呼吸します。 あなたの励ましのフィードバックを期待しています。



私は簡単なものから始めましょう-定義付きです。



スクリプト言語でのメタプログラミングは、実行時に名前空間を変更する機能を利用するプログラム作成スタイルです。



名前空間とは、クラス、メソッド、および変数(グローバル、ローカル、インスタンス変数、およびクラス変数)を意味します。 変更とは、クラス、メソッド、変数を作成、変更、削除することを意味します。



ほとんどのスクリプト言語では、名前空間はランタイムモードでのみ構築されます。 しかし、多くはこれを覚えていないので、定義でこれを強調しました。 定義からランタイムの余分な言及を削除すると、「with Benefit」というフレーズが残ります。 それが本質です。



役に立たない非プログラミングの例: eval "s = 'eval s'; eval s"







電卓





私は、幼少期にBK-0010の関数をプロットするプログラムをどのように書いたかを覚えています。 関数はハードコーディングされており、プログラムの実行中にリストから1つの関数を選択し、範囲[x0、x1]を指定することだけが可能で、Y軸に沿った範囲(見よ、プログラマーの考え!すべてとすべてを自動化できる)がプログラムによって自動的に選択されました。



私は自分のBASICプログラムを見て、エクスタシーを経験しました。 しかし、それから悲しい思いが思いつきました。 しかし、私が必要とする関数の公式をプログラムに直接組み込むことができないのは残念なことです。



Ndaa ... 8年生、1992年、キロボチェペツク市。 それ以来多くのことが流れていますが、問題は同じです!



なんで?



以下は、Ruby言語のインタラクティブな「計算機」のコードです。



行= readline
   eval(行).inspectを置きます
終わり


またはそれ以上

 while(print ">"; true)およびline = readline
   eval(行).inspectを置きます
終わり


または右

' readline 'が 必要

while line = Readline .readline( " > "

始める

eval (行).inspectを置きます

レスキュー => e

puts e.to_s + " \ n " + e.backtrace.join( " \ n "

終わり

終わり





実行例:

 artem @ laptop:〜/メタレクチャー$ ruby​​ console.rb 
 > 1 + 2
 3
 >「こんにちは」
 「こんにちは」
 > def fib(n)(0..1)=== n?  1:fib(n-1)+ fib(n-2)終了
なし
 >(0 ... 10).map {| n |  fib(n)}
 [1、1、2、3、5、8、13、21、34、55]
 > 1/0
 (評価):1: `/ 'で:0で除算
 console.rb:4
 (評価):1  
 >終了
アルテム@ラップトップ:〜/メタ講義$


スクリプト言語には、文字列を取得し、現在のコンテキストで(ほぼ) eval



呼び出された場所でプログラマーによって書かれたように、その文字列を実行するeval



メソッドがあります。



実際、 eval



ようなツールは、コンパイルされたプログラミング言語でも利用できます。



ところで、私はeval



メソッドをメタプログラミングに帰することはせず、このレッスンでは非常に有害なメソッドとさえ呼びます。 対話型ルビー| python | perl | ...-シェルは、おそらく使用すべき数少ない例の1つです。 eval



メソッドの危険性eval



以下でeval



ます。



attr_accessor





rubist初心者でもattr_accessorコンストラクトを使用してクラスインスタンスの属性を決定しますが、それらがどんな種類の獣であるかを常に認識しているわけではありません。



次のステートメントからattr_accessor



式の意味をattr_accessor



ます:code

クラスの

attr_accessor :タイトル 、: 長さ

終わり



(結果により)コードと同等

クラスの

def タイトル

@title

終わり

def title = (v)

@title = v

終わり

def length

@length

終わり

def length = (v)

@length = v

終わり

終わり



定義は次のとおりです!



肉眼で見ると、 attr_accessor



は、コードを簡潔でattr_accessor



ものにするというプログラマーの内部的な要望を満​​たすため、便利です。 attr_accessor



コンストラクトは、「クラスインスタンスの次の属性のattr_accessor



メソッドが必要」と翻訳できます。



attr_accessor



コンストラクトattr_accessor



未知の獣でさえattr_accessor



ません(読み取り-組み込みの言語コンストラクトはありません)が、自分でプログラムできる通常のメソッドです。 eval



メソッドを使用してこれを行いましょう。



def attr_accessor (*メソッド)

methods.each do | 方法 |

評価 %{

def #{メソッド}

@ #{メソッド}

終わり

def #{ method } =(v)

@ #{メソッド} = v

終わり

    
}

終わり

終わり



これで、属性名の配列を引数として受け取るattr_accessor



は、属性に対応するsetメソッドとgetメソッドを定義する各名前のコード行を実行します。



Rubyにはクラスまたはメソッド定義の概念がなかったため、 attr_accessor



ようなメソッドをattr_accessor



が登場しました。 「 class Song



」という行を書いたので、通常の計算を実行できる新しいコンテキストに移動しました。「 def xxx() ... end



」という構文は、式の1つに過ぎず、計算結果は常にnil



(Ruby v1.8では) )、サードパーティの効果は、この構築が実行されたコンテキストのクラスの「 xxx



」メソッドの外観に現れます。



関数定義は完了しましたか? クラスコンテキストに移動しましたか? どんなナンセンス? -どこから来たC ++プログラマーに尋ねます。 はい、正確に。



class Song



」は、クラス定義を前にフレーム化せず、ネームスペースのスコープが変更される特別なコンテキストに切り替えます。 つまり、このコンテキストで呼び出すことができるいくつかの新しいメソッドが表示され、特定の命令の実行の値と効果が変化するなどです。 など



テキスト「 def xxx() ... end



」は実際には式であり、Ruby仮想マシンによって実行されます。 同時に、メソッド定義の内部は実行されませんが、コードはバイトに変換され、メソッド名の下に保存されます。



Q:クラスのコンテキストはどういう意味ですか?



A:これは、式selfが何らかのクラスに等しいようなコンテキストです。



次のコードを実行します。

{1 self .inspect } からhi1を置く

クラス abc

#{ self.inspect } からhi2を置く

こんにちは

#{ self .inspect } からhi3を置きます

終わり

終わり



hi1とhi2の行が印刷されます。 追加するとhi3の行が表示されます

 Abc.new.hi


合計取得:

 artem @ laptop:〜/メタレクチャー$ ruby​​ self_in_contexts.rb 
メインからhi1
 hi2 from Abc
 #<Abc:0xb7c3d9dc>のhi3
アルテム@ラップトップ:〜/メタ講義$


あなたが書くときそれを理解する必要があります

 my_method(arg1、arg2)


次に、本質的に「 self.



」が暗黙的に前で置換されます。

 self.my_method(arg1、arg2)




ただし、これら2つの式は、場合によっては同等ではありません。
たとえば、 my_method



private



メソッドの場合、式self.my_method



private



メソッドの呼び出しでエラーをself.my_method



ます。 これらはRuby実装の機能( private



メソッド)であり、ドットを介して呼び出すことのできないメソッドがあります。





さて、話を止めてください。 上記attr_accessor



コードをattr_accessor



するようにします。

クラス モジュール

def attr_accessor (*メソッド)

methods.each do | 方法 |

class_eval %{

def #{メソッド}

@ #{メソッド}

終わり

def #{ method } =(v)

@ #{メソッド} = v

終わり

      
}

終わり

終わり

終わり



私たちは何をしましたか? Module



クラスのコンテキストにメソッド定義を配置し、 eval



class_eval



置き換えclass_eval







なぜこれをしたのですか? 理由があります:

*どのオブジェクトが利用可能になるかを理解せずにメソッドを記述するのは良くありません。 クラス( Class



クラスのインスタンス)およびモジュール( Module



クラスのインスタンス)のコンテキストで使用できるattr_accessor



メソッドを記述する必要があります。 Class



クラスはModule



クラスを継承するため、このメソッドをModule



インスタンスのメソッドとして定義するだけで十分です。この場合、モジュールとクラスの両方で使用できます。

* class_eval



メソッドにはeval



との違いがあり、特に後者は、式「 def ... end



」を実行すると、 attr_accessor



メソッド内にローカルに存在し、 attr_accessor



メソッドの実行中にのみ使用可能なメソッドの定義を作成します(これは文書化されていない機能「 def



inside def



」です)。 class_eval



メソッドは、正しいコンテキストで指定されたコードを実行するため、「 def



」が目的の結果を生成し始めます。 class_eval



メソッドclass_eval



アクティブであり、その引数が文字列ではなくブロックである場合に正確にメタプログラミングで使用されます。



これでコードが機能しました。 しかし、彼は間違っています。 「 class Module



」と「 class_eval



」のないものを含む、他の間違ったソリューションがあります。 それらの1つを次に示します。



def attr_accessor (*メソッド)

methods.each do | 方法 |

評価 %{

クラス #{ self }

def #{メソッド}

@ #{メソッド}

終わり

def #{ method } =(v)

@ #{メソッド} = v

終わり

終わり

    
}

終わり

終わり





最後のオプションは、このコンテキストで式self



が等しいかどうかに応じて、クラスのコンテキストではなくそれを呼び出して何か悪い結果を得ることができるという点で悪いです。 例:

s = " クラス "

s.instance_eval { attr_accessor :hahaha }

Array .hahaha = 3 #予想外に、Arrayにはhahaha属性があります

Array .hahahaを置きます





最も重要なこと:

説明されているeval



を使用したattr_assessor



定義は、セキュリティの欠如のために悪いです-敵の悪意からも、プログラマーの愚かさからも保護されていません: method



変数の値がメソッド名の有効な文字列でない場合、たとえば、文字列「 llalala(); puts `cat /etc/passwd`; puts



"、結果は予測できません。 プログラムの実行中にエラー(例外)が表示されない場合があります。 「ロケットがすでに飛んでいる」ときだけ驚きが来るでしょう(c)。 しかし、終わりを見つけられないときに遅く現れる間違いほど悪いものはありません。



最後に、 attr_accessor



の定義の正しいバージョンを書きます。 彼は、間違ったものとは異なり、ユニークです:



クラス モジュール

def attr_accessor (*メソッド)

methods.each do | 方法 |

method.is_a?( Symbol )でない限り、 TypeError .new( " メソッド名はシンボルではありません " )を発生させます。

define_method(メソッド) do

instance_variable_get( " @ #{ method } "

終わり

define_method( " #{ method } = "do | v |

instance_variable_set( " @ #{ method } " 、v)

終わり

終わり

終わり

終わり





デフォルト値のattr_accessor





多くの場合、デフォルト値で属性を記述します。 「 ||=



」というイディオムを使用してこれを行います。これは、「初期化されていない場合は、右にあるものによって左にあるものを初期化する」と大まかに変換します。



 class Song def length @length ||= 0 end def title @title ||= "no title" end end Song.new.length #=> 0 Song.new.title #=> "no title"
      
      





このような決定の後、新しい曲のlength



属性の値は0になります。



パイクによると、私は欲しい...このコードを私が望むように動作させてください!!!:

クラスの歌
   attr_accessor:length ,: default => 0
   attr_accessor:title ,: default => "no title"
終わり


次の行からclass_eval



を使用して、トレーニング目的専用の間違ったコードをclass_eval



します。

クラス モジュール

def attr_accessor (*メソッド)

options = methods.last.is_a?( ハッシュ )? methods.pop:{}

methods.each do | 方法 |

class_eval %{

def #{メソッド}

\#そのように書かないでください!

@ #{メソッド}   #{ " || = #{ options [ :default ] } " if options [ :default ] }

終わり

def #{ method } =(v)

@ #{メソッド} = v

終わり

      
}

終わり

終わり

終わり

奇跡があるかもしれません!!!

クラスの歌
   attr_accessor:length ,: default => 42
終わり
 Song.new.length#出力42を置きます!!!




間違ったコードも時々機能します。 しかし、これはもちろん、それを書いたプログラマーによって解雇されない理由ではありません。



するとき

クラスの

attr_accessor :length 、: default => 42

attr_accessor :title 、: default => " no title "

終わり

puts Song .new.length #outputs 42 !!!

Song .new.title #oooooops !!!





私たちは神秘的になります:

 artem @ laptop:〜/メタレクチャー$ ruby​​ bad_attr_accessor.rb 
 42
 (評価):5:「タイトル」:スタックレベルが深すぎる(SystemStackError)
	 from(評価):5:「タイトル」
	 bad_attr_accessor.rbから:27
アルテム@ラップトップ:〜/メタ講義$ 


なぜそんなに迷惑なのですか? 実際には、基本的な問題があるということです。文字列にいくつかのオブジェクトを挿入することは、単に不可能です。



デフォルト値のattr_accessor



の問題は、 attr_accessor



ように正しく解決されます。



クラス モジュール

def attr_accessor (*メソッド)

options = methods.last.is_a?( ハッシュ )? methods.pop:{}

methods.each do | 方法 |

method.is_a?( Symbol )でない限り、 TypeError .new( " メソッド名はシンボルではありません " )を発生させます。

define_method(メソッド) do

instance_variable_get( " @ #{ method } " )||

instance_variable_set( " @ #{ method } " 、オプション[ :デフォルト ])

終わり

define_method( " #{ method } = "do | v |

instance_variable_set( " @ #{ method } " 、v)

終わり

終わり

終わり

終わり





そのため、検討した例では、メタプログラミングはメソッドを定義するメソッドを記述するように見えます。



初心者のメタプログラマーがこれらの検索をグーグルで検索するのは理にかなっています:

1. ruby​​ doc attr_accessor

2. ruby​​ doc Kernel eval

3. ruby​​ docモジュールclass_eval

4. ruby​​ docオブジェクトinstance_eval

5. ruby​​ doc Object is_a?

最初のリンクは正しいです。



eval



、および不純物、キャッシング、RPC、DSLタスクを新しい抽象化レベルに導くことができる修飾子メソッド、遅延(遅延)コンピューティングの概念を継続するパターンなどについてメタプログラミングを行う方法については、ブログの次号を参照してください。



All Articles