Ruby Freaks Lounge

第1回Ruby1.9の新機能ひとめぐり(前編):YARV、Fiber、配列処理の強化

Ruby1.9.1リリース

2009年1月、Ruby1.9.1がリリースされました。Ruby1.9系列初の安定版とされるリリースです。Ruby1.9系列は、従来のRuby1.8系列と比べて、次のような特徴を持っています。

  • 高速化や省メモリ化といった最適化
  • 多言語化をはじめとするさまざまな機能拡張
  • 文法の改良・拡張
  • その他、気の利いた機能

なかでも、⁠YARVによる高速化」「M17N対応(多言語化⁠⁠」は有名で、聞いたことのある人も多いと思います。

しかしRuby1.9には、他にも様々な改善や新機能が数多くあります。今回執筆を担当する、第1回、第3回、第5回では、あまり知られていない新機能にもスポットをあてつつ、Rubyの新機能を駆け足で紹介したいと思います。

YARV(Yet Another Ruby VM)による高速化

Ruby1.9のインタプリタは、笹田耕一氏が開発している仮想機械YARV(Yet Another Ruby VM)がコアとなりました。まずRubyプログラムをYARVバイトコードにコンパイルし、そのバイトコードをYARV上で実行する、という2段階になっています。

YARVのおかげで、1.9は1.8に比べて実行速度が大きく向上しました。特に統計処理、物理計算、人工知能のような、計算処理がボトルネックとなるプログラムでは多大な恩恵を得られます。

ただし、現在の1.8のメインターゲットであるWebアプリでは、計算ではなく通信やデータベースアクセスがボトルネックになることが多いため、それほどの効果は期待できません。さらに、コンパイルフェーズが増えるために、evalやrequireなど一部のメソッドは1.8よりも遅くなることがあります(具体的にはgemやrakeといったコマンドラインツール、FastCGIなどを使わない単純なCGIなどが影響を受けます⁠⁠。

YARVの本当の成果は速度改善ではなく、Rubyのコアを近代的なVM実装に置き換えたことであるとも言われます。YARVが更なる新機能や最適化を行っていくための基盤となり、Ruby2.0に向けて開発が加速していくことを期待しましょう。

M17N(多言語化)対応

これについては、M17Nの開発担当である成瀬さんからの解説が予定されていますので、省略します。お楽しみに。

Fiberの導入

1.9で導入された、新しい制御構造です。他の言語では、コルーチン、マイクロスレッド、軽量スレッドなどと呼ばれることもあります。Fiberを使うと、プログラムの実行を中断したり、再開したりすることができます。

コード1 Fiberの使い方
# Fiberを作る (作るだけでは実行されない)
fiber = Fiber.new do
  p "fiber 1"
  Fiber.yield # 実行を中断する
  p "fiber 2"
end

p "main 1"
fiber.resume # fiberの実行を開始し、中断されたところで戻ってくる
p "main 2"
fiber.resume # fiberの実行を再開し、最後まで実行して戻ってくる
p "main 3"
図1 コード1の実行結果
"main 1"
"fiber 1"
"main 2"
"fiber 2"
"main 3"

Fiberを使うと、複数の処理を少しずつ交互に実行するようなプログラムを直感的に書けるようになります。よく言われる例では、シューティングゲームなどで、敵や弾をそれぞれ1フレームずつ動かす処理です。また、アドベンチャーゲームで画面描画処理とシナリオ進行管理を交互に行うような場合も考えられます。

実際にFiberが用いられている例としては、データベースへの複数のトランザクションを管理するNeverBlockというライブラリがあります。NeverBlockはFiberを使い、⁠直感的なトランザクション記述」「各トランザクションを交互に(非同期に)実行することによる効率性」を両立しています。

配列処理の強化

1.9では、1.8系列に比べてイテレータなどの配列処理が大幅に強化されています。

Enumeratorによってイテレータの組み合わせが容易に

Array#eachなどに代表されるイテレータをブロックなしで呼び出すことで、Enumeratorオブジェクトが得られるようになりました。Enumeratorオブジェクトとは、すごく簡単に言うと「イテレータが列挙する要素を順に入れた配列のようなもの」です。Enumeratorのおかげで、イテレータを組み合わせやすくなりました。

コード2 Enumeratorを用いてイテレータを組み合わせる
ary = [1, 2, 1, 3, 3, 1, 4, 2, 2]

# 1.8の場合
result = nil
ary.each_cons(2) do |x, y|
  if x == y
    result = [x, y]
    break
  end
end
p result #=> [3, 3]

# 1.9の場合
result = ary.each_cons(2).find {|x, y| x == y }
p result #=> [3, 3]

これはeach_cons(n)(連続するn個の要素を組にして列挙するイテレータ)を使って、最初に登場する「同じ値が2連続になっている組」を見つける例です。1.8ではeach_cons(2)が列挙する組から条件を満たすものを自力で探していますが、1.9ではEnumerable#findを組み合わせて使うことができ、記述が簡潔になりました。

ここで、⁠1.8は途中でbreakしているが、1.9のコードは最後まで列挙しているので非効率ではないか」と思った方がいるかも知れません。しかし心配は要りません。Enumeratorが「配列のようなもの」であって「配列」でないのはその点です。Enumeratorは、各要素が実際に必要になるまで元のイテレートの実行を延期します。この例では、each_consの呼び出し自体はイテレートをせずEnumeratorを返すだけです。findが呼ばれることで初めてイテレートが行われます。さらにfindは[3,3]を見つけた時点でイテレートをとめるため、each_consのイテレートもそこで止まります。要するに、余計な列挙をしたり巨大な配列を確保したりはしないということです。

おそらく最もよく組み合わせるイテレータはwith_indexでしょう。これによって、任意のイテレータでインデックスを自力管理する必要がなくなりました。

コード3 with_index の使用例
# map と with_index を組み合わせる例
p ["dog", "cat", "bear"].map.with_index {|s, i| "#{ s } #{ i }" }
  #=> ["dog 0", "cat 1", "bear 2"]

著者は1.9の新機能の中では、この機能を特に使います。

外部イテレータのサポート

Enumeratorには「内部イテレータを外部イテレータ化する」というもうひとつの顔があります。Rubyのブロックは内部イテレータといわれ、⁠イテレータから要素が渡される」のを処理するという書き方になりますが、外部イテレータはその逆で、⁠イテレータから要素を取り出し」て処理するという書き方になります。

コード4 外部イテレータの使用例
# Enumeratorの作成
e = [1, 2, 3].each

# 1つめの要素を取り出す
p e.next #=> 1

# 2つめの要素を取り出す
p e.next #=> 2

# 巻き戻すこともできる
e.rewind

# 巻き戻したら1つめの要素から取り出しなおす
p e.next #=> 1

PythonやC++など、外部イテレータが標準的な言語に慣れた人には合う機能かもしれません。ただし、この機能はFiberを用いて実装されており、内部イテレータに比べて実行速度がかなり遅いことには注意すべきです。

配列関係のメソッド追加

ArrayやEnumerableに便利なメソッドが多数追加されました。シャッフルのように何度も再発明されてきたメソッドや、記述性・可読性を向上させるメソッドです。

順列・組み合わせ系のメソッド
  • 順列を列挙する Array#permutation
  • 組み合わせを列挙する Array#combination
  • 配列と配列の直積を返す Array#product
ランダム系のメソッド
  • 配列をシャッフルする Array#shuffle
  • 配列から要素をランダムに抽出する Array#sample
Haskell に由来するメソッド
  • 先頭n個を取り出す Enumerable#take(ary[0, n] と同じ)
  • 先頭n個を捨てたものを返する Enumerable#drop(ary[n..-1] と同じ)
  • 先頭から条件を満たす限りの要素を取り出す Enumerable#take_while {}
  • 先頭から条件を満たす限りの要素を捨てたものを返す Enumerable#drop_while {}
  • 配列を循環して列挙し続ける Enumerable#cycle(通常は Enumerator 化して使います)

それぞれの詳細はマニュアルを参照してください。

以上の「配列処理の強化」の機能は、あまり周知されていないように思います。⁠YARV」「M17N」のようなキーワードがないことや、1.8.7で1.8系列にもバックポートされたため厳密には1.9の新機能でなくなったことが理由でしょうか。ですが、この機能はコードの再発明を防止し記述性を向上させる非常に優れたものですので、少しでも多くの方に知ってもらいたいと思います。

gemとrakeが組み込みに

従来はサードパーティのツールに過ぎなかったrubygems(Rubyライブラリのパッケージングツール)と、rake(make のようなビルドツール)が標準機能として組み込まれました。

rubygemsはgem installというコマンド一発でライブラリをインストールできる便利なツールです。ただし、現在はまだRuby 1.9に対応していないパッケージも多いため、本領を発揮するには時間がかかりそうです。is it Ruby 1.9というサイトで種々のパッケージが 1.9で動作したかどうかの報告をみることができますので、興味のある方は参照するとよいでしょう。

rakeはmakeのようなツールですが、Makefileに相当するRakefileがRubyによるDSLとして設計されており、Rubyに慣れたユーザなら凝ったことがしやすくなっています。

まとめ

今回は、1.9の最適化と機能拡張を中心に紹介しました。ニュースなどではYARVやM17N といったわかりやすく目立つ機能が取りざたされがちですが、Ruby1.9の真価はそれだけではありません。実際に使い始めると、配列処理の強化が非常に便利であることに気がつくと思います。

次回は、Ruby1.9の洗練された文法を中心に取り上げたいと思います。

おすすめ記事

記事・ニュース一覧