Rubyの学習において、ブロック (Block) は最も重要で強力な機能の一つです。他言語の経験者にとって、これはラムダ式や無名関数、クロージャに似た概念ですが、Rubyではこれが言語構文の核に深く組み込まれています。
この章では、ブロックの使い方と、ブロックを活用する「イテレータ」と呼ばれるメソッドを学びます。
ブロックとは、メソッド呼び出しに渡すことができるコードの塊です。メソッド側は、受け取ったそのコードの塊を好きなタイミングで実行できます。
ブロックには2種類の書き方があります。
{ ... } (波括弧): 通常、1行で完結する場合に使われます。do ... end: 複数行にわたる処理を書く場合に使われます。どちらも機能的にはほぼ同じです。最も簡単な例は、指定した回数だけブロックを実行する times メソッドです。
irb(main):001:0> 3.times { puts "Hello!" }
Hello!
Hello!
Hello!
=> 3
irb(main):002:0> 3.times do
irb(main):003:1* puts "Ruby is fun!"
irb(main):004:1> end
Ruby is fun!
Ruby is fun!
Ruby is fun!
=> 3
3.times というメソッド呼び出しの後ろに { ... } や do ... end で囲まれたコードブロックを渡しています。times メソッドは、そのブロックを3回実行します。
Rubyでは、コレクション(配列やハッシュなど)の各要素に対して処理を行うメソッドをイテレータ (Iterator) と呼びます。イテレータは通常、ブロックを受け取って動作します。
代表的なイテレータを見ていきましょう。
each は、コレクションの各要素を順番に取り出してブロックを実行します。他言語の foreach ループに最も近いものです。
|n| の部分はブロック引数と呼ばれ、イテレータが取り出した要素(この場合は配列の各要素)を受け取ります。
irb(main):001:0> numbers = [1, 2, 3]
=> [1, 2, 3]
irb(main):002:0> numbers.each do |n|
irb(main):003:1* puts "Current number is #{n}"
irb(main):004:1> end
Current number is 1
Current number is 2
Current number is 3
=> [1, 2, 3]
Note:
eachメソッドの戻り値は、元の配列 ([1, 2, 3]) 自身です。eachはあくまで「繰り返すこと」が目的であり、ブロックの実行結果は利用しません。
map は、各要素に対してブロックを実行し、そのブロックの戻り値を集めた新しい配列を返します。
irb(main):005:0> numbers = [1, 2, 3]
=> [1, 2, 3]
irb(main):006:0> doubled = numbers.map { |n| n * 2 }
=> [2, 4, 6]
irb(main):007:0> puts doubled.inspect
[2, 4, 6]
=> nil
irb(main):008:0> puts numbers.inspect # 元の配列は変更されない
[1, 2, 3]
=> nil
map は、元の配列を変換した新しい配列が欲しい場合に非常に便利です。
select は、各要素に対してブロックを実行し、ブロックの戻り値が真 (true) になった要素だけを集めた新しい配列を返します。
irb(main):009:0> numbers = [1, 2, 3, 4, 5, 6]
=> [1, 2, 3, 4, 5, 6]
irb(main):010:0> evens = numbers.select { |n| n.even? } # n.even? は n % 2 == 0 と同じ
=> [2, 4, 6]
find は、ブロックの戻り値が真 (true) になった最初の要素を返します。見つからなければ nil を返します。
irb(main):011:0> numbers = [1, 2, 3, 4, 5, 6]
=> [1, 2, 3, 4, 5, 6]
irb(main):012:0> first_even = numbers.find { |n| n.even? }
=> 2
irb(main):013:0> over_10 = numbers.find { |n| n > 10 }
=> nil
each, map, select, find といった便利なメソッドは、実は Enumerable(エニューメラブル)というモジュールによって提供されています。
Enumerable はRubyの「Mix-in(ミックスイン)」機能の代表例です。これは、クラスに「混ぜ込む」ことで、そのクラスのインスタンスに特定の機能(メソッド群)を追加する仕組みです。
Enumerable をMix-inするクラス(例えば Array や Hash, Range)が満たすべき契約はただ一つ、each メソッドを実装することです。
each メソッドさえ定義されていれば、Enumerable モジュールは each を使って map, select, find, sort, count など、数十もの便利なイテレーションメソッドを自動的に提供してくれます。
例えば、Array クラスは each を持っています。
irb(main):014:0> numbers = [1, 2, 3]
=> [1, 2, 3]
# numbers (Array) は each を持っているので...
irb(main):015:0> numbers.map { |n| n * 2 } # map が使える
=> [2, 4, 6]
irb(main):016:0> numbers.select { |n| n.odd? } # select が使える
=> [1, 3]
これは、自分で新しいコレクションクラスを作った場合でも同様です。(include については後の「モジュールとMix-in」の章で詳しく学びます)
# Enumerableモジュールを include する
class MyCollection
include Enumerable # これがMix-in
def initialize(items)
@items = items
end
# Enumerable のために each メソッドを定義する
def each
@items.each do |item|
yield(item) # ブロックに要素を渡す
end
end
end
collection = MyCollection.new([10, 20, 30])
# each を定義しただけで、map が使える!
doubled = collection.map { |x| x * 2 }
puts "Map result: #{doubled.inspect}"
# select も使える!
selected = collection.select { |x| x > 15 }
puts "Select result: #{selected.inspect}"ruby my_collection.rbMap result: [20, 40, 60] Select result: [20, 30]
このように、Rubyのイテレータの強力さは Enumerable モジュールによって支えられています。Rubyでは、**「each メソッドを持つものは、すべて Enumerable である(あるいはそう振る舞える)」**という考え方が非常に重要です。
他言語経験者の方は、for ループを使いたくなるかもしれません。
// C や Java の for ループ
for (int i = 0; i < 3; i++) {
printf("Hello\n");
}
Rubyにも for 構文は存在します。
irb(main):014:0> numbers = [1, 2, 3] => [1, 2, 3] irb(main):015:0> for num in numbers irb(main):016:1* puts num irb(main):017:1> end 1 2 3 => [1, 2, 3]
しかし、Rubyの世界では for ループはほとんど使われません。なぜなら、for は内部的に each メソッドを呼び出しているに過ぎないからです。
Rubyプログラマは、for よりも each などのイテレータをブロックと共に使うことを圧倒的に好みます。イテレータの方が、何をしているか(単なる繰り返し、変換、選択など)がメソッド名 (each, map, select) から明確であり、コードが読みやすくなるためです。
すでに出てきたように、ブロックは | ... | を使って引数を受け取ることができます。
irb(main):018:0> ["Alice", "Bob"].each do |name|
irb(main):019:1* puts "Hello, #{name}!"
irb(main):020:1> end
Hello, Alice!
Hello, Bob!
=> ["Alice", "Bob"]
また、ブロックも(Rubyのすべての式と同様に)戻り値を持ちます。ブロックの戻り値とは、ブロック内で最後に評価された式の値です。
each はブロックの戻り値を無視します。map はブロックの戻り値を集めて新しい配列にします。select はブロックの戻り値が真か偽かを判定に使います。irb(main):021:0> result = [1, 2].map do |n| irb(main):022:1* m = n * 10 # mは 10, 20 irb(main):023:1* m + 5 # ブロックの戻り値 (15, 25) irb(main):024:1> end => [15, 25]
では、どうすればブロックを受け取るメソッドを自分で作れるのでしょうか?
それには yield というキーワードを使います。
メソッド内で yield が呼び出されると、そのメソッドに渡されたブロックが実行されます。
def simple_call puts "メソッド開始" yield # ここでブロックが実行される puts "メソッド終了" end simple_call do puts "ブロック内の処理です" end
ruby yield_basic.rbメソッド開始 ブロック内の処理です メソッド終了
yield はブロックに引数を渡すこともできます。
def call_with_name(name)
puts "メソッド開始"
yield(name) # ブロックに "Alice" を渡す
yield("Bob") # ブロックに "Bob" を渡す
puts "メソッド終了"
end
call_with_name("Alice") do |n|
puts "ブロックが #{n} を受け取りました"
endruby yield_with_args.rbメソッド開始 ブロックが Alice を受け取りました ブロックが Bob を受け取りました メソッド終了
each や map のようなイテレータは、内部でこの yield を使って、コレクションの各要素をブロックに渡しながら実行しているのです。
{}(1行)または do...end(複数行)で記述します。
each, map, select など)。each を実装するクラスに map や select などの強力なイテレーション機能を提供します。for ループよりもイテレータが好まれます。|arg| で引数を受け取ることができ、ブロックの最後の式の値が戻り値となります。yield を使うと、渡されたブロックを実行できます。数値の配列 [1, 2, 3, 4, 5] があります。map イテレータとブロックを使って、各要素を文字列に変換し(例: 1 → "1")、 "1", "2", "3", "4", "5" という文字列の配列を作成してください。
array = [1, 2, 3, 4, 5]
ruby practice6_1.rb文字列の配列 ["apple", "banana", "cherry", "date"] があります。select イテレータとブロックを使って、文字数が5文字以上の果物だけを抽出した新しい配列(["apple", "banana", "cherry"])を作成してください。
array = ["apple", "banana", "cherry", "date"]
ruby practice6_2.rb