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つの式は、場合によっては同等ではありません。
たとえば、
が
メソッドの場合、式
は
メソッドの呼び出しでエラーを
ます。 これらはRuby実装の機能(
メソッド)であり、ドットを介して呼び出すことのできないメソッドがあります。
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タスクを新しい抽象化レベルに導くことができる修飾子メソッド、遅延(遅延)コンピューティングの概念を継続するパターンなどについてメタプログラミングを行う方法については、ブログの次号を参照してください。