Ruby コードブロック (Blocks)
コードブロック(Blocks)は Ruby において極めてコアなベーシック・コンセプトです。これらはメソッドにパッシング(伝達)できるコード・フラグメントであり、パワフルでフレキシブルなプログラミング・ケイパビリティを提供します。コードブロックをユースすることで、メソッド自体をモディファイ(変更)することなく、メソッドのビヘイビア(振る舞い)をカスタマイズすることが可能になります。
コードブロックの理解は、Ruby のエクスプレッシブネス(表現力)をマスターし、その特性をフル活用するためのキーとなります。本章では、コードブロックをディファイン(定義)する2つのアプローチ:do...end のユースと {} のユースについて探求します。それらのシンタックス(構文)、プレセデンス(優先順位)の差異、およびそれぞれの最適なアプリケーション・シナリオについてディスカッションします。これらのナレッジは、後続で学習するメソッド内でのコードブロックのハンドリングや、Procs、Lambdas といったハイレベル・コンセプトを深く理解するための強固な基盤となるでしょう。
1. コードブロックの理解:do...end と {}
Ruby において、コードブロックとはメソッドのコール(呼び出し)にアタッチできるコードのセグメントです。これを、パラメータとしてパッシング可能なネームレス・メソッド(無名メソッド)としてイメージすると良いでしょう。コードブロック自体はオブジェクトではありませんが、オブジェクト(すなわち Procs および Lambdas、これらは後続のモジュールでカバーします)にコンバートすることが可能です。コードブロック最大のメリットは、メソッド内部にカスタム・ロジックをインジェクト(注入)できる点にあります。
Ruby にはコードブロックをディファインする2つのアプローチが存在します:
do...endキーワードをユースする- カーリーブレース
{}をユースする
サンプルを通じてそれぞれを探索していきましょう。
2. do...end コードブロック
do...end ストラクチャーは、通常、マルチライン(複数行)のコードブロックをディファインするためにユースされます。これは do キーワードでスタートし、end キーワードで終了します。アソシエート(関連付け)されたメソッドが該当コードブロックをコールした際、do...end 内部のコードがエグゼキュート(実行)されます。
# サンプル:each メソッドと do...end のコンバイン
numbers = [1, 2, 3, 4, 5]
numbers.each do |number|
puts "カレント・ナンバーは: #{number} です"
puts "該当ナンバーの平方は: #{number * number} です"
end
puts "---"
# コンディション(条件)チェックを含んだ別のサンプル
numbers.each do |number|
puts "処理中: #{number}"
puts "#{number} は偶数です" if number.even?
endこのサンプルにおいて、numbers.each は numbers アレイ(配列)内の各エレメントをトラバース(走査)するメソッドです。do...end コードブロックは、各エレメントに対してエグゼキュートする具体的なオペレーションをディファインしています。|number| のセクションは、カレントのエレメントをパラメータとしてコードブロック内部にパッシングするためにユースされます。
3. {} コードブロック
カーリーブレース {} は、通常、シングルライン(単一行)のコードブロックをディファインするためにユースされます。マルチラインのシナリオでもユース可能ですが、コードのリーダビリティ(可読性)の観点から、マルチラインの場合は一般的に do...end をユースすることが推奨されます。do...end と比較して、{} のシンタックスはよりコンサイス(簡潔)です。
# サンプル:each メソッドと {} のコンバイン
numbers = [1, 2, 3, 4, 5]
numbers.each { |number| puts "カレント・ナンバーは: #{number} です" }
puts "---"
# コードブロック内にマルチプルなステートメントを含む別のサンプル(セミコロンまたはニューラインをユース)
numbers.each { |number|
puts "処理中: #{number}"
puts "#{number} は偶数です" if number.even?
}このサンプルは前のものと類似していますが、do...end の代わりにカーリーブレース {} をユースしています。{} 内にマルチプルなステートメントを記述する場合、コードをクリーンに保つために、do...end のマルチライン・フォーマットへリファクタリングすることが強く推奨されます。
4. コードブロック・パラメータ
do...end であっても {} であっても、パラメータをレシーブすることが可能です。パラメータはコードブロックの開始部分にある2本のパイプ | | の間にディファインされます。これらのパラメータの数量やミーニング(意味)は、該当コードブロックをコールするメソッドによってディサイド(決定)されます。
# サンプル:マルチプルなパラメータを含むコードブロック
hash = { a: 1, b: 2, c: 3 }
hash.each do |key, value|
puts "キー: #{key}, バリュー: #{value}"
end
puts "---"
hash.each { |key, value| puts "キー: #{key}, バリュー: #{value * 2}" } # シングルライン {}このサンプルにおいて、hash オブジェクトの each メソッドは、ハッシュ内の各キーバリュー・ペア (key-value pair) の key と value という2つのパラメータをコードブロックにパッシングします。
5. コアな差異:プレセデンス
do...end と {} の間の最も主要なディファレンス(違い)は、それらのプレセデンス(優先順位)にあります。
カーリーブレース {} のプレセデンスは do...end よりもハイレベル(高い)です。これは、Ruby が {} コードブロックを、そのライン上で最も近くにあるメソッドとタイトにバインド(結合)させようと試みることを意味します。これに注意を払わないと、予期せぬリザルトを招く可能性があります。対照的に、do...end のプレセデンスは低いため、メソッドチェーンや複雑なエクスプレッション(式)をハンドリングする際、そのインテント(意図)がよりクリアになる傾向があります。
# サンプル:プレセデンスのディファレンスのデモンストレーション
def some_method(arg)
puts "メソッドがコールされました。パラメータ: #{arg.inspect}"
yield if block_given? # yield については後述します。その役割はコードブロックのエグゼキュートです。
return arg
end
# do...end のユース
result = some_method([1, 2, 3]) do |x|
puts "コードブロックがエグゼキュートされました"
end
puts "---"
# {} のユース - ここのディファレンスに注意してください!
result = some_method([1, 2, 3]) { |x| puts "コードブロックがエグゼキュートされました" }
puts "---"
# プレセデンスのトラップを示す別のクラシックなサンプル
def another_method
return 1, 2, 3
end
p another_method.map { |x| x * 2 } # 正解 - コードブロックが正確に map メソッドにパッシングされました
p another_method.map do |x| x * 2 end # リザルトは期待と異なります - do...end のプレセデンスは低いため、map ではなく一番外側の p メソッドにパッシングされてしまいます最後のサンプルにおいて、{} はハイ・プレセデンスであるため、左側の最も近い map メソッドにタイトに「ホールド」されます。しかし do...end はロー・プレセデンスであるため、map をオーバーパスして、自身をライン全体の一番外側にある p メソッドにバインドしようと試み、プログラムのビヘイビアが期待と合致しなくなります。
6. どのコードブロックをユースすべきか?
以下は、do...end と {} の間でセレクトする際の一般的なガイドラインです:
do...end:マルチラインのコードブロック、特にコードブロック内に複雑なロジックが含まれる場合にユースします。また、メソッドチェーン・コールを行う場合や、レギュラー・パラメータを持つメソッドをコールし、コードブロックをそのメソッドにクリアにアソシエートさせたい場合にもユースしてください。{}:コードをコンサイスに保つためのシングルラインのコードブロックにユースします。エクスプリシット(明示的)にハイ・プレセデンスを要求する場合(例:チェーン・コール内の特定のメソッドにコードブロックをパラメータとしてパッシングする場合)にも、これをユースしてください。
6.1 サマリー比較テーブル
| プロパティ | do...end | {} |
|---|---|---|
| シンタックス | do ... end | { ... } |
| マルチラインの適性 | マルチラインのコードブロックに推奨 | ユース可能だが、マルチライン時のリーダビリティは劣る |
| シングルラインの適性 | ユース可能だが、コンサイスさに欠ける | シングルラインのコードブロックに推奨 |
| プレセデンス | ローレベル (バインディングがルーズ) | ハイレベル (バインディングがタイト) |
| リーダビリティ | 複雑なロジックに適し、ストラクチャーがクリア | シンプルなロジックに適し、コードがコンパクト |
7. 実践ユースケースとデモンストレーション
実際のデベロップメントにおいて、これら2つのコードブロックをどのようにユースするかのサンプルを見ていきましょう。
7.1 アレイのフィルタリング (select メソッド)
# do...end のユース
numbers = [1, 2, 3, 4, 5, 6]
even_numbers = numbers.select do |number|
number.even?
end
puts "偶数: #{even_numbers}" # アウトプット: 偶数: [2, 4, 6]
puts "---"
# {} のユース
odd_numbers = numbers.select { |number| number.odd? }
puts "奇数: #{odd_numbers}" # アウトプット: 奇数: [1, 3, 5]ここでは、アレイのナンバーをフィルタリングするために select メソッドをユースしています。コードブロックはフィルタリングのコンディション(true をリターンするエレメントをキープする)をディファインしています。
7.2 平方和のコンピュート
# do...end のユース
numbers = [1, 2, 3, 4, 5]
sum_of_squares = 0
numbers.each do |number|
sum_of_squares += number * number
end
puts "平方和: #{sum_of_squares}" # アウトプット: 平方和: 55
puts "---"
# {} のユース (inject メソッドとのコンバインでさらにコンサイスに)
numbers = [1, 2, 3, 4, 5]
sum_of_squares = numbers.inject(0) { |sum, number| sum + number * number }
puts "平方和: #{sum_of_squares}" # アウトプット: 平方和: 55このサンプルでは、アレイ・エレメントの平方和をコンピュートしています。{} バージョンのコードは、アドバンスドな inject メソッドとシングルラインのコードブロックを組み合わせて、極めてコンパクトなコードを実装する方法をデモンストレーションしています。
7.3 ハッシュ (Hash) のトラバース
# do...end のユース
person = { name: "Alice", age: 30, city: "New York" }
person.each do |key, value|
puts "キー: #{key}, バリュー: #{value}"
end
puts "---"
# {} のユース
person.each { |key, value| puts "#{key.capitalize}: #{value}" }7.4 メソッドチェーンとプレセデンスの実践
def modify_array(array)
array.map { |x| x * 2 }.select { |x| x > 5 }
end
numbers = [1, 2, 3, 4, 5]
modified_numbers = modify_array(numbers)
puts modified_numbers.inspect # アウトプット: [6, 8, 10]ここでは、{} のハイ・プレセデンスにより、2つのコードブロックがそれぞれ対応する map と select メソッドに正確かつタイトにバインドされています。もしこれを do...end にリプレイス(置換)した場合、コードは高い確率でエラーをスローするか、括弧を使用してエグゼキュートのシーケンスを強制的かつエクスプリシットに指定する必要が生じます。