ブロック
ブロック付きのメソッド呼び出し
主な用途は次の3つ。
- ループの抽象化
- ブロックへの機能付加。典型的にはリソース管理。
- コールバック関数・イベントハンドラ
クロージャーとしてのブロック
ブロックはクロージャである。
つまり、ブロックの中のコードに現れる自由変数はブロックの外部環境に従う。ブロックにおける自由変数は、外部環境であるブロックの外側のコンテキストで解決される。
下記の例では、ローカル変数 count がブロックの外と中で共有されている。
str = 'Hello, World'
count = 0
str.each_line do |line|
print line
count += 1
end
print count # => 1
ローカル変数や self 、 self に紐づいているインスタンス変数、メソッド呼び出しなどは全てブロックの中でも外側と同じように利用できる。
def some_method
3.times { p self } # self は 3 ではなく、some_method の self
end
環境の保存
ブロックが参照している外部環境は、ブロックが存在する限り保存されている。メソッド実行は終了しても、内部のコードブロックはメソッド実行時のローカル変数を利用する。
下記の例では、 create_counter はメソッド内部のコードブロックを Proc オブジェクトに変換して呼び出し側に返す。
Proc#call メソッドを呼ぶとコードブロックを実行する。
create_counter メソッドの実行コンテキストにおけるローカル変数 count は、 メソッドが返した Proc 以外からは参照できない。
つまり、内部状態を隠蔽することができる。
def create_counter
count = 1
return Proc.new do
count += 1
p count
end
end
counter = create_counter
p counter.class # => Proc
counter.call # => 2
counter.call # => 3
counter2 = create_counter
counter2.call # => 2
counter.call # => 4
counter2.call # => 3
ブロックパラメータ
メソッドに渡したブロックは、メソッドから呼び返されるときに引数を受け取ることができる。また、ブロックは独自のローカル変数 = ブロックパラメータを持つことができる。
a = "str"
[1, 2, 3].each { |a| p a } # この a は、上の a とは別物
p a # => "str"
ここまでにわかることから、注意しなければならない2点。
- 外部のローカル変数と同名のブロックパラメータを用いない
- 外部で既出のローカル変数は、ブロック内外で共有される(クロージャー)
ブロック付きのメソッドの定義
yield 式
def foo_bar_baz
yield "foo"
yield "bar"
yield "baz"
end
# または
def foo_bar_baz
# ブロックを与えられていないとき、enum_for で Enumerator を生成して返す
return enum_for(:foo_bar_baz) unless block_given?
%w[ foo bar baz ].each do |item|
yield item
end
end
foo_bar_baz do |item|
puts item
end
# => foo
# bar
# baz
map もどき
def my_map
[yield(1), yield(2), yield(3)]
end
p my_map { |i| i + 1 }
# => [2, 3, 4]
Proc
呼び出し側のブロックをオブジェクトとして取得したい場合、Procオブジェクトを使う。
下記の例における &handler を、ブロック引数と呼ぶ。
class SleepyPerson
def register_handler(&handler)
@event_handler = handler
end
def wake_up!
@event_handler.call Time.now, "woke up"
end
end
john = SleepyPerson.new
john.register_handler { |time, message| p [time, message] }
john.wake_up!
Proc からブロック
proc = Proc.new { puts "Proc was called" }
3.times(&proc)
# => Proc was Called
# Proc was Called
# Proc was Called
まとめ
とりあえずこれだけ大事そう
- 外部で既出のローカル変数は、ブロック内外で共有される(クロージャー)
- & 修飾で ブロック <-> Procオブジェクト