こんにちは、かつコーチです。
「eachやmapでdo ... endを書いているけど、これって一体何なんだろう」。
なんとなく使えているけれど、仕組みまでは理解していない、という方は多いのではないでしょうか。
今回は、Rubyのブロックの正体と、自作メソッドでブロックを受け取るためのyieldの使い方を解説します。
読み終える頃には、eachやmapのようなメソッドを自分でも作れるようになります。
ブロックとyieldの基本
ブロックとは何か
ブロックとは、メソッドの呼び出しに付け加えることができる、処理のかたまりのことです。
これまでに登場したeachやmapの{ }や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
ブロックは「そのメソッド呼び出し限りの一時的な処理」であるのに対し、Procやlambdaは変数に代入して使い回せるオブジェクトという違いがあります。
「同じ処理を複数のメソッドで再利用したい」場合は、ブロックではなくProcやlambdaを検討するとよいでしょう。
この違いは奥が深いテーマなので、詳しくは別の記事で改めて取り上げます。
まとめ
この記事のポイント
- ブロックは、メソッドの呼び出しに付け加えられる処理のかたまり
yieldは、渡されたブロックをメソッドの中で実行するためのキーワード- ブロックが渡されているかは
block_given?で判定できる - ブロックの引数は数が合わなくてもエラーにならず、余分な値は無視される
Procやlambdaはブロックと似ているが、変数として使い回せる点が異なる
次に読むべき記事
ブロックの仕組みが分かったところで、次は「クラスとオブジェクト指向の基本」に進み、Rubyのオブジェクト指向の考え方を学んでいきましょう。
「メソッドの定義と引数」も未読の方は、あわせて読んでおくと理解がより深まります。