#ブロックとスコープ
rubyにおいて、スコープを思い通りに飛び越えて変数を共有、変更するために、
ブロックは引数+αの要素として、メソッドに渡すイメージ。無名引数のようなもの。yieldで渡されたブロックを呼び出すことができる。
##通常のスコープ
def mymethod
x = "Goodbye"
yield("cruel")
end
x = "Hello"
my_method {|y| "#{x},#{y} world" } # => "Hello, cruel world"
def just_yield
yield
end
ブロックを定義する時、その時点でその場にある束縛を所得する。
メソッド内にも別でxという変数は定義されているので、この場合、goobyeの方でxが書き換えられてしまいそうだが、実際にはブロックが定義された時のxを見ている。メソッドにある束縛はブロックからは見えない。
top_level_variable = 1
just_yield do
top_variable += 1
local_to_block = 1 #新しい束縛
end
top_level_variable # => 2
local_to_block # => Error!
このようにblockの中で変数を定義することは可能ではあるが、ブロックの外に出ることはできない。
v1 = 1
class MyClass
v2 = 2
local_variables # => [:v2]
def my_method
v3 = 3
local_variables
end
local_variables
end
obj = MyClass.new
obj.my_method # => [:v3]
local_variables # => [:v1, :obj]
また、rubyでは新しいスコープに入る度に以前の束縛は新しい束縛に置き換えられる。よって、MyClass内に入ると、v1はスコープを外れて見えなくなる。
ただし、インスタンス変数はそのインスタンスが保持するものであるため、メソッドから、、同じオブジェクトのメソッドを呼び出したとしても、変わることはない。
###スコープゲート
スコープが新しくスコープをオープンする場所は3つある。
・クラス定義
・モジュール定義
・メソッド
プログラムはスコープをclass,defなどのキーワードによって、判別するため、これらのスコープゲートを超えてローカル変数を渡したい場合には、単にclassやdefなどを使わない定義の仕方をすれば良い。
my_var = "成功"
MyClass = Class.new do
puts "クラス定義の中は#{my_var}"
define_method :my_method do
"メソッド定義の中も#{my_var}"
end
end
このように記述することで、スコープゲートを飛び越えることができる。
###instance_eval
class MyClass
def initialize
@v = 1
end
end
obj = MyClass.new
obj.instance_eval do
self # => #<MyClass:igaerig324 @v = 1>
@v #=> 1
end
インスタンスに渡されたブロックはレシーバをselfにしてから評価されるため、レシーバのprivateメソッドや、@vのようなインスタンス変数にもアクセスすることができる。
また
v = 2
obj.instance_eval{ @v = v }
obj.instance_eval{ @v } # => 2
このようにインスタンス変数をローカル変数で書き換えることもできる。
instance_exec
instance_evalのもう少し柔軟になったバージョンにinstance_execがある。
class C
def initialize
@x = 1
end
end
class D
def twisted_method
@y = 2
C.new.instance_eval{ "@x: #{@x}, @y: #{@y}" }
end
end
D.new.twisted_method #=> "@x: 1, @y: "
instance_evalはselfをレシーバにするため、呼び出し側のインスタンス変数はスコープから溢れてしまう。
これを防ぐためにinstance_execで@yの値をブロックに渡せば良い。
class D
def twisted_method
@y = 2
C.new.instance_eval(@y){ |y| "@x: #{@x}, @y: #{y}" }
end
end
##Procオブジェクト
Procはブロックをオブジェクトにしたもの。
inc = Proc.new {|x| x + 1}
inc.call(2) # => 3
このように、ブロックをオブジェクトとして、保存して、後から評価することができる。
dec = lambda {|x| x - 1}
dec.class #=> Proc
dec.call(2) #=> 1
メソッドに特別な引数を用意して、ブロックに束縛を割り当てることもできる。
def math(a, b)
yield(a, b)
end
def do_math(a, b, &operation)
math(a, b, &operation)
end
do_math(2, 3){|x, y| x * y} #=> 6
##メモ
ensure
エラーが起きようがおきまいがどっちみち実行する。