この概念の本質は、ブロックが作成されたコンテキストで見えるように、どのブロックでも、 「周囲の世界全体」であるということです。 全世界(名前空間)がブロックに含まれているのではなく、世界(名前空間)の視点が固定されていると言う方が正しいです。
次の例を確認した後、この段落を再度読み直してください。
例を理解するには、ブロック、メソッドの概念を独自に理解しておくと便利です。
Proc#call
Proc#call
、
lambda
構成体、およびクラスインスタンス変数(インスタンス変数は名前が犬で始まる変数)およびクラス変数(クラス変数は名前が2つの犬で始まる変数)の概念とともに:
-
Proc
Proc
式で直接作成できる名前のない(匿名の)メソッドと呼ばれるブロックのクラス。 - 式
b.call(*args)
はブロックb
を実行し、実行結果を返します。 呼び出しの代わりに、角括弧を使用できます。 -
lambda {|a,...| ... }
lambda {|a,...| ... }
-ブロックを作成します。たとえば、b = lambda {|x,y,z| x+y+z}
b = lambda {|x,y,z| x+y+z}
は、3つの数値を加算するブロックを作成します。特に、式b[1,2,3]
は6
を返します。 - ブロックはラムダを使用して作成されるだけでなく、メソッドが呼び出されたときに自動的に構築され、その後に構築
{ ... }
またはdo ... end
続きます。 たとえば、ary.inject{|a,b| a * b}
ary.inject{|a,b| a * b}
は、2つの数値の乗算を実行するブロックをinject
メソッドに渡します。 - インスタンス変数はオブジェクト内に存在し、デフォルトでnilに初期化されていると見なされます。
- クラス変数はクラス内に存在し、デフォルトでは初期化されていないと見なされます。 予備的な初期化なしで式で使用すると、
Exception
「uninitialized class variable .. in ...
」が発生します。
したがって、コード例:
例1
a = 1 <br>
b = lambda { puts a }<br>
b.call # 1 <br>
a = 2 <br>
b.call # 2 <br>
# - <br>
例2
class Abc <br>
attr_accessor :bar <br>
def foo <br>
@bar ||= 0 <br>
x = 5 <br>
lambda { puts @bar , x, self .class; }<br>
end <br>
end <br>
<br>
x = 10 <br>
a = Abc .new<br>
b = a.foo<br>
b.call # 0, 5 Abc <br>
a.bar += 1 <br>
x = 10 <br>
b.call # 1, 5, Abc <br>
# bar a b, <br>
# ( ) -- <br>
# ; - <br>
# foo, @a x, <br>
# , <br>
# , foo . <br>
<br>
lambda
を使用して作成されたブロックと、メソッドに渡されたブロックの両方で、中括弧を使用して装飾され、
do ... end
構文を使用して、ブロックが閉じられます。
最後の例では、クラス
Abc
インスタンスaで
foo
メソッドを呼び出しました。
このメソッド内では、
@bar
インスタンス変数が初期化され、ブロックが返されます。
この変数と、ローカル変数
x
および
self.class
値を
self.class
ます。
このコードを実行すると、ブロックが祖国にどれだけアタッチされているか、すべての考えと動機がそこにあることがわかります。
文字列「
b.call
」が
b.call
コンテキストでは、
@bar
変数
@bar
表示
b.call
ません(つまり、このコンテキストには存在しません)。
それでも、ブロック
b
実行すると
@bar
オブジェクト
a
変数
@bar
値が出力されますが、それはここでは不適切です。 これは、ブロックがオブジェクト
a
foo
メソッドの実行のコンテキストで作成され、このコンテキストではオブジェクト
a
すべてのインスタンス変数が表示されるという事実によって説明されます。
したがって、オブジェクトの内部コンテキストは、オブジェクト内で作成されたブロックを使用して引き出し、何らかの機能の結果として外部に転送できます。
例3
class Abc <br>
attr_accessor :block <br>
def do_it <br>
@a = 1 <br>
block.call<br>
end <br>
end <br>
<br>
c = 1 <br>
a = Abc .new<br>
a.block = lambda { puts " c= #{ c } " }<br>
a.do_it # 1; <br>
# - <br>
# <br>
<br>
a.block = lambda { puts " @a= #{ @a .inspect } " }<br>
a.do_it # nil, .. @ , <br>
# " " a.block. <br>
# a.block Abc#foo <br>
# Abc#foo a.block <br>
同じことを繰り返しますが、ブロックはメソッドに関連付けられたブロックとして作成され、
lambda
構造は使用されません。
class Abc <br>
def do_it (&block)<br>
@a = 1 <br>
block.call<br>
end <br>
end <br>
<br>
c = 1 <br>
a = Abc .new<br>
a.do_it {puts " c= #{ c } " } <br>
a.do_it { puts " @a= #{ @a .inspect } " }<br>
<br>
コンテキストとは何ですか?
これは名前空間の特定の視点であり、そこから何かが見え、何かが見えず、何かが独自の方法で見られます。
たとえば、このメソッドが表示され、
self
がこのオブジェクトに等しいオブジェクトのインスタンス変数は、メソッドの本体から表示されます。 他のオブジェクトのインスタンス変数は表示されません。
特殊な式
self
は、各コンテキストで独自の方法で定義できるメソッドとして考えると便利です。
コンテキストを変更する理由は、
def
および
class
コンストラクトです。 それらは通常、インスタンス変数、クラス変数の可視性の変化、および
self
表現の値の変化をもたらします。
通常のブロックも、それが作成されたコンテキストを含むものの、新しいコンテキストです。 ブロックは、独自のローカル変数(およびC内)と引数(特別なローカル変数として解釈される必要があります)を持つことができます。
実際、コンテキストの概念には
Ruby
独自の固有のマッピングがあります-それはクラスのオブジェクトです
Binding
Binding
各ブロックには
binding
があり、この
binding
は
eval
メソッドの2番目の引数として渡すことができます:「このコンテキストでこのコードを実行する」:
例4
class Abc <br>
attr_accessor :x <br>
def inner_block <br>
lambda {| x | x * @factor }<br>
end <br>
end <br>
<br>
a = Abc .new<br>
b = a.inner_block<br>
eval ( " @factor = 7 " , b.binding)<br>
puts b[ 10 ] # 70 <br>
eval ( " @x = 6 * @factor " , b.binding)<br>
puts ax # 42
しかし、もちろん、そのように書く必要はありません。 オブジェクトのコンテキストでコードを実行するには、単に
instance_eval
使用し
instance_eval
。
例5
class Abc <br>
attr_accessor :x <br>
end <br>
<br>
a = Abc .new<br>
a.instance_eval( " @factor = 7 " )<br>
a.instance_eval( " @x = 6 * @factor " )<br>
puts ax # 42
計算時間
閉鎖などの喜びのために、あなたは支払う必要があります。
- ブロックへのリンクが生きている場合、対応するコンテキストは生きており、与えられたコンテキストから見えるすべてのオブジェクトは生きています(まずローカル変数が意味されます)。 したがって、これらのオブジェクトをガベージコレクタで収集する権利はありません。 クロージャーは、それらをすべて一度にフックするように見えました。 スマートポインターの概念に精通している人にとっては、コンテキスト(バインディング)を作成すると、すべての可視オブジェクトのref_counterが1増加し、したがってコンテキストが破棄される(このコンテキストで作成されたすべてのブロックが削除されると発生する)すべての可視オブジェクトのref_counterを1減らします。 しかし、実際にはこれは行われません。 Rubyのガベージコレクターは、スマートポインターとは異なる概念に基づいて構築されています( コピーオンライトフレンドリーなガベージコレクターのステータス -Ruby フォーラム 、特にwww.ruby-forum.com/attachment/2925/mostlycopy-en.pptを参照してください) callccのメモリリークと同様に )
- 実際のクロージャーには、名前空間の可視性だけでなく、呼び出しスタックも含まれます。 Rubyでは、スタックにアクセスできます。つまり、インスタンス化されたコンテキスト(
Binding
クラスのオブジェクト)の絶対的な真正性を実際のコンテキストの概念で実現するには、呼び出しスタックとこのスタックにあるすべてのオブジェクトの両方を保存する必要があり、これが実際の問題になります。 呼び出しスタックへのアクセスの例:
def backtrace <br>
begin <br>
raise Exception .new( '' )<br>
rescue Exception =>e<br>
e.backtrace[ 1 ..- 1 ]<br>
end <br>
end <br>
<br>
def f <br>
g<br>
end <br>
<br>
def g <br>
puts backtrace.join( " \n " )<br>
end <br>
<br>
f<br>
<br>
その結果、次の出力が得られます。make_rescued.rb:15:「g」 make_rescued.rb:11:「f」 make_rescued.rb:18
- 最適化の1つは、ブロックでローカル変数などが使用されていない場合、ブロックのコードが分析され、コンテキストが作成されないことです。
ary.map{|i| i*i}
またはusers.map{|u| e.email}
users.map{|u| e.email}
、私は閉鎖に対処したくないでしょう。 しかし、原則として、ブロックで使用されるものを予測可能な名前空間から予測することは単に不可能です。これは、原則として、ブロック内でeval
またはメソッド呼び出しがブロック内で発生する可能性があるためです。彼が望むことは何でもしてください。 また、式send(m, *args)
恐れる必要があります。これは、send('eval', *args)
になることがあるsend('eval', *args)
です。 「block = class << Object.new; lambda { ... } end
」のように、最小限のコンテキストでブロックを作成できます。 おそらく、最適化のために理にかなっています(まず、クロージャーにしがみついている呼び出しスタックを取り除きたい)glob_do ... end
という形式の新しい言語構成をglob_do ... end
、共通のコンテキスト(self
が特別main
オブジェクトに等しいグローバルコンテキスト)を作成します。
参照資料
- www.javapassion.com/rubyonrails/ruby_meta.pdf-プレゼンテーションでは、このブログで書くことになる多くのことを示しています
- www.infoq.com/presentations/nutter-jruby-jvm-lang-summit-オフプラットフォームの動的言語をJVMにもたらすことの苦痛-動的言語用のJavaバイトコードでコンパイラを記述する問題について話し、一般的な考え方を示します動的言語インタープリターを作成するときに解決されるタスク
- 1.メタプログラミングパターン-25 kyu。 評価方法
- 2.メタプログラミングパターン-22 kyu。 小さなところで再利用-バン!