ブロック(クロージャ)とスコープ


Ruby固有のスコープの扱いについて追記。

ブロックとスコープ

メソッド呼び出しの直後にブロックを指定することができる。
例えば、以下においてメソッドfuncを呼び出す際に引数1と同時にdoから始まりendで終わるブロックを指定している。
ブロックの中ではx1という変数に2を代入していて、それがブロックの評価値となる。
ブロック付きで呼び出されたメソッドの中では、ブロックの評価値をyeildという特殊変数で受け取ることができる。
つまり、メソッドfuncの中では、引数y1=1と、ブロックの評価値2の和の評価が行われる。


def func y1
  y1 + yield
end # 3
func (1) do
  x1 = 2
end

なお、ブロックはdo..endだけでなく、{..}でも書ける。
Rubyの掟的には、複数行のブロックはdo..end、1行のブロックは{..}とのこと。


func (1) { x1 = 2 }

ちなみに、ブロックの中と外はスコープが異なる。
ブロックの中で確保した変数x1は、スコープの外からは見えない。
ブロックの中で確保した変数はブロックの評価が完了した時点で破棄される。


func (1) { x1 = 2 }
p x1 # Exception

破棄されるのはブロックで初めて確保した変数であって、
ブロックの外部の変数をブロック内で使用した場合は、
ブロック内での操作がブロック外の変数に影響する。

例えば、以下のように予めブロックの外で変数x1を確保しておき、
ブロックにより加算するという状況で、ブロック内のx1は紛れもなくブロック外のx1。
x1はブロックにより束縛される(キリっ)。
ブロックの評価後に評価すると、確かにブロック内の加算が行われた後。


x1 = 10
func (1) { x1 += 2 } # 13
p x1 # 12

Rubyの特殊なスコープ事情

変数の束縛について追記。他の言語では動きそうな以下のコードはRubyでは動かない。
Rubyではオブジェクトやメソッドの壁を超えて変数を参照することはできない。
(Railsではインスタンス変数を使って超えちゃってるけど..)


fuga = 20
def myfunc
  p fuga
end

ブロックを使うことで初めて実行時の環境(コンテキスト,文脈)を共有できる

ここで一般化して良いのかわからないけども、
処理の生成時の変数を束縛するものをクロージャというらしい。
実行時の環境を動的に使えて便利だな、ぐらいの認識だったけど、
変数の束縛がポイント。

ブロックの引数

ブロック、つまりクロージャの引数の定義方法と使いかたは以下の通り。
クロージャ内の先頭で引数を|で囲む。かなり特殊に見える。慣れだろうか。


def func2 x2, y2
  x2 + yield(y2,30)
end
p func2(10,20){|x,y| x+y} # 60

メソッド側でブロックが指定されたか調べる

どんなケースでそんな使いかたをするのか..
メソッド側で呼び出し時にブロックが指定されてか否かを調べることができる。


def func3 x3,y3
  return x2 + yield(y2,30) if block_given?
  0
end
p func3(10,20){|x,y| x+y} # 60
p func3(10,20) # 0