0
1

More than 3 years have passed since last update.

【Ruby】ブロック

Last updated at Posted at 2021-01-04

ブロック

ブロック付きのメソッド呼び出し

主な用途は次の3つ。

  1. ループの抽象化
  2. ブロックへの機能付加。典型的にはリソース管理。
  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オブジェクト

参照

0
1
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1