Ruby 2.0.0 の注意点やその他の新機能

はじめに

本稿では Ruby 2.0.0 の注意点や、他の特筆するべき機能追加についてまとめています。

なお、公式には NEWS ファイルに変更点がまとまっていることになっています。

気を付ける必要がある非互換

文字コードまわりの非互換

書いた人:naruse

デフォルトのスクリプトエンコーディングの変更

Magic Comment を書いていないファイルの文字列リテラルのエンコーディング (default script encoding) が UTF-8 に変更されました。[#6679]

Ruby 1.9 においては、default script encoding は US-ASCII でした。しかし、以下のようなメリット・デメリットを考えた結果、上記の通り UTF-8 へと変更することになりました。

  • 21世紀の現代においてはほとんどのコードが UTF-8 であるため、デフォルトが UTF-8 であった方が便利
  • 1.9 向けの Magic Comment の書かれたコードはこの変更の影響を受けない
  • 1.9 向けの Magic Comment の無いコードは、従来 US-ASCII や ASCII-8BIT になっていた String だった文字列が UTF-8 になるため、文字列処理の速度が低下する可能性がある
  • 1.8 時代の UTF-8 以外のエンコーディングで書かれたコードには Magic Comment はないが、UTF-8 以外の文字列はたいていの場合 UTF-8 としては不正なバイト列としてエラーになるため問題になる可能性は低い

典型的には、Magic Comment を書いていないコードでバイナリをエスケープを使いながらソースコードに埋め込んでいる場合にはこの変更によって問題が発生する可能性があります。この場合、Magic Comment を明示的に書くようにするか、[#6767] で追加された Sring#b を用いて、ASCII-8BIT へと明示的に変更してください。

iconv ライブラリが削除された

拡張ライブラリ iconv が削除されました。[#6322]

今後は String#encode や Encoding::Converter、あるいはどうしても iconv が必要な場合は iconv.gem を用いてください。

-K を用いたときに warning が出るようになった

1.8 時代にはお世話になったオプション、-K を指定した場合、-w をつけているときには警告を表示するようになりました。

String#lines が Enumerator ではなく配列を返すようになった

書いた人:yhara

String クラスには、文字列に対して列挙を行う each_line、each_char、each_byte、each_codepoint というメソッドがあり、それらと同じ動作をする lines、chars、bytes、codepoints メソッドがありました。

Ruby 2.0.0 ではこれらのうち、lines、chars、bytes、codepoints の方が Enumerator ではなく配列を返すようになりました。このため、1.9 で

 # 文字列の最後の行を返す
 str.lines.to_a.last
 # 文字列の二番目の文字を返す
 str.chars.to_a[1]

のように書いていたプログラムが、2.0 では

 # 文字列の最後の行を返す
 str.lines.last
 # 文字列の二番目の文字を返す
 str.chars[1]

のように to_a なしで書けるようになりました。

一方、この影響で、

 str.chars.with_index{ ... }

のようなプログラムがエラーになるようになりました。配列には with_index メソッドがないからですね。この場合は、以下のように each_char の方を使用してください。

 str.each_char.with_index{ ... }

なお IO、StringIO、ARGF にも同名のメソッドがありますが、2.0.0 では IO#bytes 等は deprecated になり、使うと warning が表示されるようになりました。 IO#bytes の返り値の型が String#bytes と異なるのは気持ち悪いけど、かといってファイル全体を配列化したいケースはあまりなさそうなので *1、IO の方は deprecated にしようという判断がされました。*2 *3

この場合も bytes ではなく each_byte の方を使うことが推奨されています。

関連チケット:http://bugs.ruby-lang.org/issues/6670

inspect が to_s を使わなくなった

書いた人:ささだ

inspect を定義していないクラスでは、to_s が使われていましたが、2.0.0 か ら Object#inspect が使われることになりました。

 class C
   def to_s
     "C!!"
   end
 end
 puts C.new.inspect
 #=> 1.9 では C!!
 #=> 2.0 では #<C:0x300da4>

従来のように inspect を使いたければ、明示的に inspect が to_s を使うよう に定義するようにしましょう。

数値 (Integer と Float) が frozen になった

書いた人: ささだ

Integer (Fixnum, Bignum) と Float オブジェクトが必ず freeze されるようになりました。多分、無いと思いますが、これらの数値オブジェクトにインスタンス変数や特異メソッドなどを追加するプログラムは動かなくなりました。そのようなプログラムは窓から外に放り出しましょう。

関連チケット:http://bugs.ruby-lang.org/issues/3222, http://bugs.ruby-lang.org/issues/6936

その他の新機能

新しいシンボルリストの記法

書いた人:ささだ

シンボルの配列 [:a, :b, :c] を、%i(a b c) のように省略して書けるようになりました。

%w (文字列展開しない) 、%W (文字列展開する) に対応して、%i (文字列展開しない) 、%I (文字列展開する) があります。

p %w(a b#{1} c) #=> ["a", "b\#{1}", "c"]
p %W(a b#{1} c) #=> ["a", "b1", "c"]

p %i(a b#{1} c) #=> [:a, :"b\#{1}", :c]
p %I(a b#{1} c) #=> [:a, :b1, :c]

Module#prepend の追加

書いた人:ささだ

Module#include に似た、Module#prepend という機能が追加されました。Module#include と異なり、prepend するクラス (モジュール) よりもメソッド探索順序を前にもってくる仕組みになります。

詳しくは、下記に記事をまとめてみたのでご覧下さい。

Enumerable#size の追加

書いた人:yhara

Enumerable に size というメソッドが追加されました。Enumerable なオブジェクトの要素の個数を返すメソッドには count がありますが、count は個数だけ知りたいときでも要素をすべて列挙してしまうという欠点がありました。

size は Enumerable を include している各クラスで適切にオーバーライドされていて、要素をすべて列挙することなしに個数を教えてくれます。例えば、Ruby 2.0 では「1000 個の中から 3 個を選ぶ組み合わせの数」の計算を

 p [*1..1000].combination(3).size
 #=> 166167000

のように書けるようになりました (count を使うと長さ 166167000 の配列をメモリ上に確保しようとするため、とても長い時間がかかってしまいます) 。

なお、個数が事前に分からないようなケースでは、size は nil を返します。たとえば「ファイルの中の行の数」はファイルを全部読むまで分からないため、

 File.open("log.txt"){|f|
   p f.each_line.size
 }

は nil を表示します。

関連チケット:https://bugs.ruby-lang.org/issues/6636

to_h メソッドの追加

書いた人:yhara

Ruby には to_a や to_s という、オブジェクトを配列や文字列に変換する一連のメソッドがあります。Ruby 2.0 ではこれに to_h という、オブジェクトをハッシュに変換するメソッドが加わりました。

具体的には、Struct、OpenStruct、ENV を to_h でハッシュに変換できるようになっています。またコーナーケースとして、Hash は to_h で self をそのまま返します。nil は to_h で空のハッシュ ({}) を返します。*4

これから書くプログラムでは、オブジェクトのハッシュとしての表現を返すようなメソッドは to_h という名前をつけると良いでしょう。

Kernel#__dir__ メソッドの追加

書いた人:ささだ

File.dirname(__FILE__) と同じようなことができる Kernel#__dir__ メソッドが追加されました。__dir__ だけでそのファイルのディレクトリ名が取得できます。

Array#bsearch の追加

書いた人: nari

Array#bsearch というメソッドが追加されました*5。このメソッドでは、ソートされた配列*6について、指定した条件の要素をバイナリサーチを利用して見つけます。プログラミングコンテストなどの機会に自前で bsearch を実装しなくて済むので、個人的にはかなり嬉しい機能です。

まず、例を示します。下記の例はすべて Ruby 本体の RDoc から引用しています。

ary = [0, 4, 7, 10, 12]
ary.bsearch {|x| x >=   4 } #=> 4
ary.bsearch {|x| x >=   6 } #=> 7
ary.bsearch {|x| x >=  -1 } #=> 0
ary.bsearch {|x| x >= 100 } #=> nil

bsearch はブロックの引数に配列内の任意の要素が渡され、ブロックは true/false のいずれかを返す必要があります。そして、それぞれ以下のような振る舞いをします。

  • true を返す場合、探している要素はブロックに渡った任意の要素である、もしくはそれより大きなインデックスにある
  • false を返す場合、探している要素はブロックに渡った任意の要素より小さなインデックスにある

例もあわせて説明を読むと、なんとなく理解できそうですね。

さて、実は bsearch は二つのモードがあり、ここまでは find-minimum というモードについてのみ説明してきました。 ここからは二つ目のモードである find-any モードについても説明していきます。

# ほとんどのユースケースでは find-minimum モードしか使わないと思うので、
# ここからは興味のある人だけ。

それぞれのモードはブロックの返り値の種類のよって切り替わります。 ブロックで true/false を返す場合は find-minimum モードで動き、数値を返す場合は二つ目の find-any モードで動きます。

find-any モードは、条件に合致する範囲(i...j)にある、任意の要素を見つける機能です。ブロックが返す数値によって以下のような振る舞いになります。

  • 正数を返す場合、ブロックに渡る ary[k] は i...j の範囲外であり、探している要素は k より大きなインデックスにある
  • 0 を返す場合、ブロックに渡る ary[k] は i...j の範囲内(i <= ary[k] < j)
  • 負数を返す場合、ブロックに渡る ary[k] は i...j の範囲外であり、探している要素は k より小さなインデックスにある
ary = [0, 4, 7, 10, 12]
# 4 <= x < 8 の条件にあう値を探す
ary.bsearch {|x| 1 - x / 4 } #=> 4 or 7
# 8 <= x < 10 の条件にあう値を探す(ないので nil を返す)
ary.bsearch {|x| 4 - x / 2 } #=> nil

例もあわせて読むとなんとなくわかりそうな感じですね! ちなみに find-any モードは libc の bsearch(3) を参考にしているそうです。

Fiber ローカルではないスレッドローカル変数の機能の追加

書いた人:ささだ

Thread#[] によって、スレッドローカル変数(スレッド間で共有する変数)を扱うことができましたが、Fiber を切り替えると無効になってしまうと言う問題がありました (これらの値を Fiber ローカル変数と言うことにします)。

そこで新しく、Fiber を切り替えても共通で使えるスレッドローカル変数を扱うための API が次のように用意されました。

  • Thread#thread_variable_get(sym) で sym というスレッドローカル変数を取得
  • Thread#thread_variable_set(sym, obj) で sym というスレッドローカル変数をセット
  • Thread#thread_variables ですべてのスレッドローカル変数を取得
  • Thread#thread_variable?(sym) で sym というスレッドローカル変数があるかどうかチェック

殆どのケースでは、Thread#[] によって操作する、Fiber 間で独立した Fiber ローカル変数を利用すると良いと思いますが、時々この新しい API が必要になるかもしれません。注意して使って下さい。

スタックサイズの指定

書いた人:ささだ

スレッドおよびファイバが利用する VM およびマシンスタックのサイズが環境変数でそれぞれ指定できるようになりました。

  • RUBY_THREAD_VM_STACK_SIZE: スレッドを作る時に作成する VM スタックサイズ(デフォルト: 128KB (32bit CPU) or 256KB (64bit CPU))
  • RUBY_THREAD_MACHINE_STACK_SIZE: スレッドを作る時に作成するマシンスタックサイズ(デフォルト:512KB or 1024KB)
  • RUBY_FIBER_VM_STACK_SIZE: ファイバを作る時に作成する VM スタックサイズ(デフォルト:64KB or 128KB)
  • RUBY_FIBER_MACHINE_STACK_SIZE: ファイバを作る時に作成するマシンスタックサイズ(デフォルト:256KB or 512KB)

デフォルトのスタックサイズも大きくなっています。普通に使う分にはあまり気にしなくても良いと思います。

この記事も参考にして下さい:Ruby VM アドベントカレンダー #21 VM のスタックサイズチューニング

デバッグ機能の強化

書いた人:ささだ

Ruby 2.0.0 から、いくつかのデバッグ機能が追加されました。

  • set_trace_func を置きかえる TracePoint
  • caller() の返値をもっと使いやすくする caller_locations()
  • オブジェクトの参照関係を取り出す ObjectSpace.reachable_objects_from()

などなどです。詳しくは今号の YARV Maniacs 【第 11 回】 最近の YARV の事情 (から参照されている Ruby VM アドベントカレンダー)をご参照下さい。


*1 いちばんありそうなのは行の列にする場合ですが、この用途には IO#readlines という昔からあるメソッドが存在します。

*2 knu さんは、将来 Enumerable とインデックスアクセスの機能を併せ持つ LazyArray のようなクラスを返り値にすることを考えているようです。

*3 評判がすごく悪ければ、2.0.1 で元に戻ることはあるかも知れません :-p

*4 これによって、opts[:foo] が nil かどうかを気にせずに hash = opts[:foo].to_h のように書けるわけですね。

*5 ちなみに Range#bsearch もあってそっちも似たような感じ。

*6 バイナリサーチ (二分探索) は配列がソートされていることを前提に行う探索アルゴリズムなので、配列がソートされていない場合の動作は未定義です。