【Ruby】ブロックとyieldの使い方を初心者向けに解説

Ruby

こんにちは、かつコーチです。

eachmapdo ... endを書いているけど、これって一体何なんだろう」。

なんとなく使えているけれど、仕組みまでは理解していない、という方は多いのではないでしょうか。

今回は、Rubyのブロックの正体と、自作メソッドでブロックを受け取るためのyieldの使い方を解説します。

読み終える頃には、eachmapのようなメソッドを自分でも作れるようになります。

ブロックとyieldの基本

ブロックとは何か

ブロックとは、メソッドの呼び出しに付け加えることができる、処理のかたまりのことです。

これまでに登場したeachmap{ }do ... endが、まさにブロックです。

[1, 2, 3].each { |n| puts n }

[1, 2, 3].each do |n|
  puts n
end

この2つは書き方が違うだけで、意味はまったく同じです。

一般的には、1行で完結する短い処理には{ }を、複数行にわたる処理にはdo ... endを使うことが多いです。

なぜブロックが必要なのか

ブロックが便利な理由は、「メソッドの外から処理の中身だけを差し込める」ことにあります。

たとえばeachメソッドは、「配列の要素を1つずつ取り出す」という部分は自分で担当し、「取り出した要素をどう扱うか」はブロックとして呼び出し側に任せています。

numbers = [1, 2, 3]

numbers.each { |n| puts n }        # 表示する
numbers.each { |n| puts n * 10 }   # 10倍して表示する

同じeachメソッドでも、渡すブロックを変えるだけで、まったく違う処理を実行できます。

この「処理の中身だけを差し替えられる」柔軟さが、ブロックの最大のメリットです。

yieldでブロックを呼び出す自作メソッドを作る

では、自分で作ったメソッドの中で、ブロックを実行するにはどうすればよいのでしょうか。

そこで使うのがyieldです。

yieldは、「メソッドの呼び出し側から渡されたブロックを、その場で実行する」ためのキーワードです。

def greet
  puts "処理を開始します"
  yield
  puts "処理が終了しました"
end

greet { puts "こんにちは、かつコーチです" }
# => 処理を開始します
# => こんにちは、かつコーチです
# => 処理が終了しました

greetメソッドの中に書かれたyieldの位置で、呼び出し側が渡したブロック(puts "こんにちは、かつコーチです")が実行されます。

yieldに引数を渡すと、その値をブロック変数として受け取ることもできます。

def greet
  yield("かつコーチ")
end

greet { |name| puts "こんにちは、#{name}さん" }
# => こんにちは、かつコーチさん

yield("かつコーチ")で渡した値が、ブロック側の|name|に渡っています。

よくあるつまずきポイント・エラー対処

ブロックを渡さずにyieldを呼んでエラーになる

yieldは、ブロックが渡されていることを前提にしています。

ブロックなしでyieldを呼び出すメソッドを実行すると、エラーになります。

# ❌Before:ブロックを渡さずにyieldを含むメソッドを呼び出す
def greet
  yield
end

greet

このコードを実行すると、次のエラーが出ます。

LocalJumpError (no block given (yield))

私は最初、既存の自作メソッドを別の処理から呼び出したときに、このエラーに遭遇しました。

「ブロックを渡さなければいけない」というメソッドの前提を、呼び出し元のコードだけ見て把握できておらず、原因調査に時間がかかった経験があります。

対処法は主に2つあります。

# ✅After1:block_given?で、ブロックの有無を判定してから実行する
def greet
  if block_given?
    yield
  else
    puts "ブロックが渡されていません"
  end
end

greet # => ブロックが渡されていません
greet { puts "こんにちは" } # => こんにちは

# ✅After2:呼び出し側で必ずブロックを渡す
def greet
  yield
end

greet { puts "こんにちは" } # => こんにちは

block_given?メソッドは、「そのメソッドにブロックが渡されているかどうか」をtrue/falseで返してくれます。

ブロックの有無で処理を分岐させたいメソッドを作る場合は、block_given?を使うのが定石です。

ブロック変数の数を勘違いしてハマる

ブロックに渡す値の数と、ブロック変数(| |の中身)の数が合わないと、意図しない挙動になることがあります。

# ❌Before:yieldで2つの値を渡しているのに、ブロック変数を1つしか受け取っていない
def show_pair
  yield(1, 2)
end

show_pair { |a| puts a } # => 1(2の方は無視されてしまう)

Rubyのブロックは、引数の数が合わなくてもエラーにならず、余分な値を黙って無視してしまいます。

私はこの挙動を知らず、2つ目の値がどこかで消えていると思い込み、無関係な箇所を延々と調べてしまったことがあります。

# ✅After:受け取りたい値の数だけブロック変数を用意する
def show_pair
  yield(1, 2)
end

show_pair { |a, b| puts "a=#{a}, b=#{b}" } # => a=1, b=2

エラーにならずに値が欠けるケースは、他の言語のエラー処理に慣れているほど見落としやすいので注意が必要です。

応用・一歩先の使い方

自作のeachメソッドを作ってみる

ここまでの知識を使うと、標準のeachと同じような挙動をするメソッドを自作できます。

def my_each(array)
  i = 0
  while i < array.length
    yield(array[i])
    i += 1
  end
  array
end

my_each([10, 20, 30]) { |n| puts n * 2 }
# => 20
# => 40
# => 60

my_eachは内部で配列をwhileループで走査しつつ、要素を1つずつyieldでブロックに渡しています。

普段何気なく使っているeachが、内部的にはこれと近い仕組みで動いていることが実感できます。

Procやlambdaとの違いに触れておく

ブロックと似たものに、Procオブジェクトlambdaがあります。

# ブロックは1つのメソッドに1回しか渡せない
def calculate
  yield(10, 20)
end
puts calculate { |a, b| a + b } # => 30

# Procはオブジェクトとして変数に代入し、使い回せる
add = Proc.new { |a, b| a + b }
puts add.call(10, 20) # => 30

# lambdaもオブジェクトとして扱えるが、引数チェックが厳密
multiply = lambda { |a, b| a * b }
puts multiply.call(10, 20) # => 200

ブロックは「そのメソッド呼び出し限りの一時的な処理」であるのに対し、Proclambda変数に代入して使い回せるオブジェクトという違いがあります。

「同じ処理を複数のメソッドで再利用したい」場合は、ブロックではなくProclambdaを検討するとよいでしょう。

この違いは奥が深いテーマなので、詳しくは別の記事で改めて取り上げます。

まとめ

この記事のポイント

  • ブロックは、メソッドの呼び出しに付け加えられる処理のかたまり
  • yieldは、渡されたブロックをメソッドの中で実行するためのキーワード
  • ブロックが渡されているかはblock_given?で判定できる
  • ブロックの引数は数が合わなくてもエラーにならず、余分な値は無視される
  • Proclambdaはブロックと似ているが、変数として使い回せる点が異なる

次に読むべき記事

ブロックの仕組みが分かったところで、次は「クラスとオブジェクト指向の基本」に進み、Rubyのオブジェクト指向の考え方を学んでいきましょう。

「メソッドの定義と引数」も未読の方は、あわせて読んでおくと理解がより深まります。

タグ: #Ruby #中級者向け #基本文法

タイトルとURLをコピーしました