Happy new year, and welcome back to Ruby Magic! この冬のエピソードでは、バインディングとスコープに飛び込みます。
前回は、ブロック、プロック、ラムダを比較して、Rubyのクロージャについて見てきました。
クロージャは、環境を持つファーストクラス関数です。 環境は、クロージャが作成されたときに存在した変数へのマッピングです。
これまで、Ruby のファーストクラス関数に相当するものを見てきましたが、都合よく環境のことはスキップしてきました。 このエピソードでは、Ruby がバインディングを通じてレキシカル スコープをどのように扱うかを調べることにより、クロージャ、クラス、およびクラス インスタンスに対してその環境がどのように機能するかを見ていきます。
Lexical Scope
プログラミングにおいて、スコープはコードの特定の部分で利用できるバインディングを指します。 バインディング、またはネームバインディングは、変数の名前とその値のように、名前をメモリ参照にバインドします。 スコープは、self
意味、呼び出せるメソッド、利用できる変数を定義します。
Ruby では、ほとんどの現代のプログラミング言語と同様に、(動的スコープとは対照的に)しばしばレキシカル スコープと呼ばれる静的スコープを使用します。 現在のスコープはコードの構造に基づいており、コードの特定の部分で利用可能な変数を決定します。 たとえば、メソッド、ブロック、およびクラス間でコードがジャンプすると、スコープが変更されます。
1 2 3 4 5 6 |
def bar foo = 1 foo end bar # => 1 |
この方法では、ローカル変数をメソッド内に作成して、それをコンソールへプリントしています。 変数はメソッド内部で作成されるため、スコープ内にあります。
1 2 3 4 5 6 7 |
foo = 1 def bar foo end bar # => NameError (undefined local variable or method `foo' for main:Object) |
この例では、変数をメソッドの外部で作成します。 メソッド内でこの変数を呼び出すと、変数がスコープ外であるため、エラーが発生します。 つまり、メソッドは、引数として渡されない限り、自分自身の外側の変数にアクセスできません。
1 2 3 4 5 6 7 |
@foo = 1 def bar @foo end bar # => 1 |
ローカル変数はローカルに使用できますが、インスタンス変数はクラス インスタンスのすべてのメソッドで使用できます。
継承されたスコープと proc バインディング
これまでの例で見てきたように、スコープはコード内の位置に基づいています。 メソッドの外部で定義されたローカル変数は、メソッドの内部ではスコープされませんが、インスタンス変数にすることで利用できるようになります。 メソッドは独自のスコープを持ち、独自のバインディングを持つため、メソッドはその外部で定義されたローカル変数にアクセスできません。
Procs (ブロックおよびラムダを含む、拡張) は異なります。 proc がインスタンス化されるたびに、ブロックが作成されたコンテキスト内のローカル変数への参照を継承するバインディングが作成されます。
1 2 |
foo = 1 Proc.new { foo }.call # => 1 |
この例では、変数 foo
を 1
にセットしています。 内部的には、2行目で作成したProcオブジェクトが新しいバインディングを作成しています。
バインディングは proc が初期化されるときに作成されるので、変数が定義された後までブロックが呼び出されない場合でも、変数を定義する前に proc を作成することはできないのです。
1 2 3 |
proc = Proc.new { foo } foo = 1 proc.call # => NameError (undefined local variable or method `foo' for main:Object) |
procを呼ぶと、その変数がprocのbindingで定義されていないためNameError
が生成されることになります。
1 2 3 4 |
foo = 1 proc = Proc.new { foo } foo = 2 proc.call # => 2 |
しかしながら、メインコンテキストで定義した後に変数を変更できるのは、その変数のコピーではなくその参照が proc の束縛に保持されるからである。
1 2 3 |
foo = 1 Proc.new { foo = 2 }.call foo #=> 2 |
この例では、foo
変数が proc 内でも外でも同じオブジェクトを示していることが分かります。
バインディング
現在のスコープを追跡するために、Ruby はバインディングを使用します。バインディングは、コード内の各位置での実行コンテキストをカプセル化するものです。 binding
メソッドは、現在の位置でのバインディングを記述した Binding
オブジェクトを返します。
1 2 |
foo = 1 binding.local_variables # => |
このバインド オブジェクトには #local_variables
というメソッドがあり、現在のスコープで使用できるすべてのローカル変数名が返されます。
1 2 |
foo = 1 binding.eval("foo") # => 1 |
コードは#eval
メソッドを使用して、バインド上で評価することが可能です。 上の例は、単に foo
を呼び出すだけでも同じ結果が得られるので、あまり有用ではありません。 しかし、バインディングは受け渡し可能なオブジェクトなので、もっと面白いことに使うことができます。 975>
A Real-Life Example
さて、安全なガレージでバインディングについて学んだので、ゲレンデに持ち出して、雪の中で遊んでみましょう。 Rubyの内部でバインディングが使われているのはもちろんですが、バインディングオブジェクトが明示的に使われる場面もあります。
1 2 3 4 5 6 7 8 9 10 |
require 'erb' x = 1 def y 2 end template = ERB.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`") template.result(binding) # => "x is 1, y() returns 2, self is `main`" |
この例では、x
という変数、y
というメソッドを作成し、両方を参照する ERB テンプレートを作成しています。 そして、現在のバインディングを ERB#result
に渡し、テンプレート内の ERB タグを評価して、変数が記入された文字列を返します。
裏側では、ERB は Binding#eval
を使用して、渡されたバインディングの範囲内で各 ERB タグのコンテンツを評価します。 上記の例で動作する簡略化された実装は次のようになります:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class DiyErb def initialize(template) @template = template end def result(binding) @template.gsub(/<%=(.+?)%>/) do binding.eval() end end end x = 1 def y 2 end template = DiyErb.new("x is <%= x %>, y() returns <%= y %>, self is `<%= self %>`") template.result(binding) # => "x is 1, y() returns 2, self is `main`" |
DiyErb
クラスは初期化時にテンプレート ストリングを受け取ります。 その #result
メソッドはすべての ERB タグを見つけ、その内容を評価した結果でそれらを置き換える。
#result
メソッドを呼び出すときに現在のバインディングを渡すことにより、eval
コールは明示的に渡すことなく、メソッドの外部、さらにはクラスの外部で定義された変数にアクセスすることが可能になります。 私たちは、スコープとクロージャをつらぬいた後、さらに深く入りました。 私たちはあなたを森で見失っていないことを望みます。 ビンディングについてもっと学びたい、または他の Ruby のトピックに飛び込んでみたいことがあれば、ぜひお知らせください。
私たちについてきてくれてありがとうございます。 もし、このような不思議な旅がお好きなら、Ruby Magic を購読して、月に一度、新しい記事を発行する際にメールを受け取ってみてはいかがでしょうか。