Rubyの面白さを理解するためのメソッド、ブロック、Proc、lambda、クロージャの基本:若手エンジニア/初心者のためのRuby 2.1入門(8)(3/3 ページ)
オープンソースのオブジェクト指向プログラミング言語「Ruby」の文法を一から学ぶための入門連載。最新版の2.1に対応しています。今回は、Rubyのメタプログラミングを学ぶ上での基礎知識となるメソッド、ブロック、Proc、lambda(ラムダ)、クロージャなどの基本的な使い方について。
分かりにくいが便利なProcとlambda(ラムダ)
手続きをオブジェクト化
ここまでで、メソッドに手続きそのものを渡す方法としてのブロックを紹介しました。実は、「Proc」クラスや「lambda(ラムダ)」という仕組みを使うことで、ブロックとして書いた手続きそのものをオブジェクト化して引き回す方法があります。block03.rbに、その例を示しましょう。
procedure1 = Proc.new { |word| puts word } procedure2 = proc { |word| puts word } procedure3 = lambda { |word| puts word } procedure4 = ->(word) { puts word } procedure5 = -> { puts "cluck" } procedure1.call("meow") procedure2.call("quack") procedure3.call("oink") procedure4.call("bowwow") procedure5.call
$ ruby block03.rb meow quack oink bowwow cluck
1行目はProc.newを使う方法で、2行目はprocメソッドを使う方法、3行目はlambdaメソッドを使う方法です。Proc.newはprocと比較して、よりシンプルに書けるという以外の違いはありません。4・5行目は「lambda」メソッドの別の書き方で、引数のカッコは省略もできます。どの記法を利用してもProcクラスのオブジェクトが生成されるので、そのまま変数に代入できます。
これらの方法で作成したProcオブジェクトは、callメソッドによって7〜11行目のように呼び出せます。callメソッドに与えた引数が、そのままブロック引数に対応します。
メソッドに与えるブロックとProcオブジェクトの関係を理解するために、メソッドに与えたブロックをProcオブジェクトとして利用したり、Procオブジェクトをブロックとして与えたりする例をblock04.rbに示します。
def repeat1(n) for i in 1..n puts "#{i} =>" yield end end def repeat2(n, &procedure) for i in 1..n puts "#{i} =>" procedure.call end end procedure = -> { puts "quack" } repeat1(3, &procedure) repeat2(3) { puts "meow" }
$ ruby block04.rb 1 => quack 2 => quack 3 => quack 1 => meow 2 => meow 3 => meow
repeat1メソッドはblock01.rbにおけるrepeatと全く同じです。repeat2メソッドは、引数として「&(アンパサンド)」から始まる「procedure」を用意しています。このように、「&」から始まる引数を定義すると、メソッドに与えられたブロックをProcオブジェクトに変換したものを変数として受け取ることができます。
また、15行目で変数「procedure」にProcオブジェクトを代入し、「repeat1」の引数として「&」を付加して利用しています。このように記述すると、メソッドに与えたProcオブジェクトを、メソッドの定義元でyieldを用いて呼び出せるようになります。
Proc.newとlambdaの違い
Proc.newもlambdaメソッドもProcオブジェクトを生成しますが、returnの挙動に違いが生じます。block05.rbにその違いを示します。
def hoge procedure = Proc.new { return "return from Proc.new" } procedure.call return "return from hoge" end def fuga procedure = -> { return "return from lambda" } procedure.call return "return from fuga" end puts hoge puts fuga
$ ruby block05.rb return from Proc.new return from fuga
hogeはProc.newによって生成したProcオブジェクトの中でreturnを記述した場合で、fugaはlambdaによるProcオブジェクトをの中でreturnを記述した場合の例になります。
実行結果より、Proc.newの場合はreturnに出会った瞬間に、Procオブジェクトが生成されたメソッドを抜けるという動作になっていることが分かります。対するlambdaの場合は、returnが実行されると単にブロックを抜け、以下に続く処理が実行されていることが分かります。
つまり、lambdaによって生成されたProcオブジェクトは、Proc.newによって生成されたProcオブジェクトよりもメソッドに近い動作を行うといえます。
補足「procとlambda、どちらを使えばよいか」
ここまで紹介したように、Procオブジェクトを生成するためには、さまざまなやり方が存在します。ですので、procとlambda、どちらを使えばよいのか迷うことになります。それらの違いが問題とならない場合はどちらでも良いのですが、プロジェクトの慣例に倣うのが無難でしょう。
筆者の場合、もし自分で選べる状況であれば、よりスマートに書けるlambdaの「->」記法を使うようにしています。そもそも、lambdaが生まれた背景には、ErlangやHaskellなどの関数型言語の「ラムダ式」という仕組みがあります。それらの言語でも、ラムダ式を記述する場合に記号として「->」を用います。lambdaの出自が関数型言語なので、それに倣うのは自然なことでしょう。
コンテキストを包み込む「クロージャ」
Rubyは「クロージャ」という概念を、Procオブジェクトによって実現しています。クロージャとは、ある手続きの引数に指定した変数以外の変数を、その手続きが定義されたコンテキストによって解決する仕組みを指します。
言葉で説明すると、どうしても抽象的になって分かりづらいので、block06.rbにクロージャの性質を示す例を挙げてみます。
def increase n = 0 -> { n += 5 } end 3.times { p increase.call } puts "==================" increase_proc = increase 3.times { p increase_proc.call }
$ ruby block06.rb 5 5 5 ================== 5 10 15
これを初めて見る方は、頭が混乱するかもしれません。ややこしいので一つずつ解きほぐしていきましょう。
まず、1〜4行目で、「increase」メソッドを定義しています。increaseメソッドは、まず変数「n」を0に初期化して、そのnを使ってn自身に5を足し込むProcオブジェクトを生成して呼び出し元に返しています。
6行目の実行では3回とも5が出力されており、11行目の実行では実行されるたびに5が足し込まれています。この違いは、6行目ではincreaseメソッドが実行されるたびにProcオブジェクトが生成され、11行目の実行では、10行目に生成したProcオブジェクトを再利用していることによって起こります。
Procオブジェクトを紹介したときに、「これは手続きそのものをオブジェクト化したものだ」と説明しました。しかしながら、本当のところは、「手続きと、その手続きが定義されたコンテキスト(環境)をオブジェクト化したもの」と言った方が正しいです。つまり、Procオブジェクトを生成するとき、その手続きのみならずコンテキストまで「包み込んで」オブジェクト化しているのです。
先ほどの動作の違いは、この「コンテキストを包み込んでいる」という性質によって説明できます。6行目の実行では実行のたびにProcオブジェクトが生成されているので、おのおののProcオブジェクトが持つコンテキストは別々のコンテキストとなるため5が3回出力されます。対する11行目の実行では、10行目で生成したProcオブジェクトを変数increase_procに代入し、3回callしています。
1回目の実行で、increase_procに包み込まれているコンテキストにおける変数nに5が足し込まれます。2回目の実行においても、同様にincrease_procに包み込まれているコンテキストにおける変数nが利用されるので、その値は、5に5を足し込んだ10となります。3回目も同様です。つまり、Procオブジェクトが同一であれば、そこに包み込まれているコンテキストも同一である、というわけです。このような変数のスコープを、「レキシカルスコープ」と言ったりもします。
クロージャの仕組みがあるおかげで、eachメソッドなどに与えるブロックの中で安心してローカル変数を使えます。
prefix = "@" ["a", "b", "c"].each do |s| puts prefix + s end
$ ruby block06.rb @a @b @c
block07.rbは、ブロックとeachメソッドを組み合わせた典型的な例です。変数prefixはブロックの外で定義されていますが、コンテキストを包み込むクロージャの仕組みのおかげで、eachメソッドの定義元でブロックを実行したとき、意図通りにprefixに「@」が代入されているというわけです。
Rubyで一番面白いメタプログラミングを理解するために
連載第8回にあたる今回は、今まで何気なく使っていたメソッドやブロックについて掘り下げて解説しました。これらの知識は、クラスやモジュールの知識と合わせて、筆者がRubyで一番面白いと感じるメタプログラミングに通じる基盤となります。
次回は、メタプログラミングに入る前に、スレッドや入出力、例外処理など、残りのRubyの基本機能について解説します。お楽しみに!
- Rubyで逆ポーランド変換機を作りgem作成&コマンドの使い方
- 難しいが強力! Rubyのメタプログラミング、self、特異クラス/メソッド、オープンクラスとモンキーパッチ
- RubyのThread、Fiber、Kernel、forkで並列処理やプロセスの深淵へ
- RubyのFile/IOクラスで入力と出力、ファイルの読み取りと書き込み、フィルター作成
- Rubyの例外とその捕捉――基本のbegin〜rescue〜endからensure、else、retry、後置rescueまで
- Rubyの面白さを理解するためのメソッド、ブロック、Proc、lambda、クロージャの基本
- Rubyのオブジェクト指向におけるクラスとモジュール、継承、Mixin、アクセス制御の使い方
- RubyのNumericとTimeで数値と時間をさまざまな操作・演算・判定
- RubyのString/Regexpクラスによる強力な文字列操作/正規表現
- RubyのRangeクラスと範囲オブジェクト、範囲演算子、イテレーターの使い方
- Rubyの配列、ハッシュテーブルを表現するArray、Hashクラスの使い方
- Ruby 2.1の基本構文/基本文法まとめ&Pryの使い方
- Rubyプログラミングを始めるための基礎知識とインストール
著者プロフィール
麻田 優真(Rails技術者認定シルバー試験問題作成者)
イタリア、ローマ生まれ。中学生のころHSPに初めて触れ、プログラミングの楽しさを知る。オープンソースやハッカーカルチャーを好む。C#からRubyに転向したときに、動的型付け言語の柔軟性やメタプログラミングの魅力に感動し、Rubyとともにプログラマーとしての人生を歩む決意を固める。
現在は奈良先端科学技術大学院大学で学生として所属するかたわら、株式会社アジャイルウェアでプログラマーとして従事。Ruby on Railsによるコンシューマー向けのWebサービスの開発などに尽力している。
好きなメソッドは、define_method。
Twitter:@Mozamimy、ブログ:http://blog.quellencode.org/
監修者プロフィール
山根 剛司(Ruby業務開発歴7年)
兵庫県生まれ。1997年からベンチャー系のパッケージベンダーで10年間勤務。当時、使用していた言語はJavaとサーバーサイドJavaScript。
2007年よりITコンサル会社に転職し、Rubyと出会って衝撃を受ける。基幹システムをRuby on Railsで置き換えるプロジェクトに従事。それ以来Ruby一筋で、Ruby on Railsのプラグインやgemも開発。
2013年より、株式会社アジャイルウェアに所属。アジャイルな手法で、Ruby on Railsを使って企業向けシステムを構築する業務に従事。
Ruby関西所属。
Twitter:@spring_kuma、Facebook:山根 剛司
Copyright © ITmedia, Inc. All Rights Reserved.
関連記事
- 3万5000円の参加費でも内外から580人の参加登録:公用語に英語、「再起動」したRubyKaigi 2013が東京で開催
いったん終了していたRubyコミュニティ主催の年次イベントが再開。技術色、国際色を強め、盛況のうちに幕を閉じた - IT業界 転職市場最前線(48):Web業界、今から行くならRubyエンジニアが狙い目?
不況で冷え込んでいたIT業界の転職市場に、回復の兆しが見え始めている。だが、業種や職種によって採用数や条件に大きな差異が生まれている。転職市場の動向を追い、自身のキャリア戦略立案に生かしてほしい。 - 安定版のメジャーバージョンアップ:Ruby 2.0.0がリリース、大規模化対応の機能などを搭載
生誕20年となる節目を迎えて、プログラミング言語「Ruby」の最新版がリリースされた - 新バージョンで何が変わるのか、Rubyはどこへ向かうのか:まつもと×笹田、Ruby 1.9を語る
- いよいよ始まるRuby 1.9への移行:開発コアメンバが語るRubyの今とこれから(前編)
- Rubyの今後の進化の方向性とは?:開発コアメンバが語るRubyの今とこれから(後編)
- 互換性や脆弱性の問題にどう対応していくのか:Rubyが抱える課題、NaClの前田氏が講演
- Rails Hub情報局:「なんでRubyなんか作った!? 迷惑だ!」に対するMatzの答え
- Rails Hub情報局:Rubyはイノベーション言語として選ばれている
- Rails Hub情報局:Rubyのまつもと氏は、一発屋で終わるのか?
- Rails Hub情報局:Rubyに魔法は要らない
- 数々の“スペル”で高度なプログラミング:Rubyの魔術
- 晴読雨読@エンジニアライフ:『たのしい開発 スタートアップRuby』――なぜRubyistたちはあれほど楽しそうなのか
- Ruby 1.9.3に対応:「JRuby 1.7.0」登場、1年半ぶりのメジャーアップデート
JavaVM上のRuby実装「JRuby」の最新版となる「JRuby 1.7.0」が、10月22日にリリースされた。