Rubyの学習において、ブロック (Block) は最も重要で強力な機能の一つです。他言語の経験者にとって、これはラムダ式や無名関数、クロージャに似た概念ですが、Rubyではこれが言語構文の核に深く組み込まれています。
この章では、ブロックの使い方と、ブロックを活用する「イテレータ」と呼ばれるメソッドを学びます。
ブロックとは、メソッド呼び出しに渡すことができるコードの塊です。メソッド側は、受け取ったそのコードの塊を好きなタイミングで実行できます。
ブロックには2種類の書き方があります。
{ ... } (波括弧): 通常、1行で完結する場合に使われます。do ... end: 複数行にわたる処理を書く場合に使われます。どちらも機能的にはほぼ同じです。最も簡単な例は、指定した回数だけブロックを実行する times メソッドです。
3.times というメソッド呼び出しの後ろに { ... } や do ... end で囲まれたコードブロックを渡しています。times メソッドは、そのブロックを3回実行します。
Rubyでは、コレクション(配列やハッシュなど)の各要素に対して処理を行うメソッドをイテレータ (Iterator) と呼びます。イテレータは通常、ブロックを受け取って動作します。
代表的なイテレータを見ていきましょう。
each は、コレクションの各要素を順番に取り出してブロックを実行します。他言語の foreach ループに最も近いものです。
|n| の部分はブロック引数と呼ばれ、イテレータが取り出した要素(この場合は配列の各要素)を受け取ります。
Note:
eachメソッドの戻り値は、元の配列 ([1, 2, 3]) 自身です。eachはあくまで「繰り返すこと」が目的であり、ブロックの実行結果は利用しません。
map は、各要素に対してブロックを実行し、そのブロックの戻り値を集めた新しい配列を返します。
map は、元の配列を変換した新しい配列が欲しい場合に非常に便利です。
select は、各要素に対してブロックを実行し、ブロックの戻り値が真 (true) になった要素だけを集めた新しい配列を返します。
find は、ブロックの戻り値が真 (true) になった最初の要素を返します。見つからなければ 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 を持っています。
これは、自分で新しいコレクションクラスを作った場合でも同様です。(include については後の「モジュールとMix-in」の章で詳しく学びます)
ruby my_collection.rbこのように、Rubyのイテレータの強力さは Enumerable モジュールによって支えられています。Rubyでは、**「each メソッドを持つものは、すべて Enumerable である(あるいはそう振る舞える)」**という考え方が非常に重要です。
他言語経験者の方は、for ループを使いたくなるかもしれません。
Rubyにも for 構文は存在します。
しかし、Rubyの世界では for ループはほとんど使われません。なぜなら、for は内部的に each メソッドを呼び出しているに過ぎないからです。
Rubyプログラマは、for よりも each などのイテレータをブロックと共に使うことを圧倒的に好みます。イテレータの方が、何をしているか(単なる繰り返し、変換、選択など)がメソッド名 (each, map, select) から明確であり、コードが読みやすくなるためです。
すでに出てきたように、ブロックは | ... | を使って引数を受け取ることができます。
また、ブロックも(Rubyのすべての式と同様に)戻り値を持ちます。ブロックの戻り値とは、ブロック内で最後に評価された式の値です。
each はブロックの戻り値を無視します。map はブロックの戻り値を集めて新しい配列にします。select はブロックの戻り値が真か偽かを判定に使います。では、どうすればブロックを受け取るメソッドを自分で作れるのでしょうか?
それには yield というキーワードを使います。
メソッド内で yield が呼び出されると、そのメソッドに渡されたブロックが実行されます。
ruby yield_basic.rbyield はブロックに引数を渡すこともできます。
ruby yield_with_args.rbeach や 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" という文字列の配列を作成してください。
ruby practice6_1.rb文字列の配列 ["apple", "banana", "cherry", "date"] があります。select イテレータとブロックを使って、文字数が5文字以上の果物だけを抽出した新しい配列(["apple", "banana", "cherry"])を作成してください。
ruby practice6_2.rb