この記事は Speee Advent Calender の25日目の記事です。
開発部 R&D グループの村田です。所謂フルタイム CRuby コミッターの一人です。
今日は 12/25 です。数時間後には Ruby 2.5.0 がリリースされている予定です。 皆さんはもう RC1 を試しましたか?え?毎日 trunk をビルドして使っているって?とても素晴らしいですね。
しかし、そうじゃ無い人も大勢いると思います。ですから、ここでは Ruby 2.5.0 で何がどう変わったのか、インパクトが大きいものを選んで最終確認をしておきましょう。
まとめ方は以下のようにしました*1。
- 言語仕様の変更
- 組み込みクラスの変更
- パフォーマンス改善
言語仕様の変更
トップレベルで定義された定数の検索
トップレベルで定義された定数はObjectクラスの下に定義されたことになります。
TWO = 2 Object::TWO #=> 2
一方、全てのクラスはObjectクラスを継承しています (親クラスに BasicObject を指定しない限りは)。 そのため、Ruby 2.4 までは、トップレベルで定義された定数を Object クラスのサブクラスの下で定義された定数であるかのようにアクセスできました。しかし、この挙動はプログラマーが意図したものでない可能性が高いため、これまでは次のように警告を出していました。
# Ruby 2.4 まで class Stuff; end Stuff::TWO #=> warning: toplevel constant NUM referenced by Stuff::TWO #=> 13
Ruby 2.5 では、この機能が削除され、次のように NameError が発生するようになっています。
# Ruby 2.5 から class Stuff; end Stuff::TWO #=> NameError: uninitialized constant Stuff::TWO
この変更によって、const missing をフックして定数の自動定義を実現している場合 (例えば Rails など) に、自動定義される定数と同じ名前の定数をトップレベルに定義することで自動定義が動かなくなる問題が解決しています。
この変更は、定数検索で親クラスを遡る時に Object クラスを特別扱いすることで実現されています。Kernel モジュールや BasicObject クラスはこれまで通りの扱いなので、それらの下に定義された定数はそれらのサブクラスをクラスパスに指定してアクセス可能です。
# これは今までと変わらない module Kernel THREE = 3 end class BasicObject FOUR = 4 end Stuff::THREE #=> 3 Stuff::FOUR #=> 4
参考リンク
do ... end ブロック内に直接 rescue/else/ensure が書けるようになった
この変更は多くの人が嬉しい変更では無いかと私は予想しています。 do ... end
形式のブロックで、 begin ... end
で全体を囲まなくてもブロック内で発生した例外を処理する rescue, else, ensure 節が記述できるようになりました。
# Ruby 2.4 まで lambda do begin raise 'err' rescue $! #=> #<RuntimeError: err> else # no exception case ensure # finalization end end
# Ruby 2.5 から lambda do raise 'err' rescue $! #=> #<RuntimeError: err> else # no exception case ensure # finalization end
お、シンタックスハイライトがうまく動いてませんね!!*2 さすが新構文だ。
参考リンク
文字列中の式展開を Refinements でカスタマイズできるようになった
文字列中の式展開は、文字列中に #{...}
で埋め込まれた式の評価結果を to_s
した結果が展開される機能です。
Ruby 2.4 までは、式展開で呼ばれる to_s
メソッドには Refinements が効きませんでした。
この制限は意図的なものではなかったらしく、Ruby 2.5 からは Refinements が効くように変更されました。
参考リンク
制御端末に表示されるバックトレースが見やすくなった
Ruby 2.5 では、制御端末に表示されるバックトレースが今までとは逆順になり、最も最近のメソッド呼び出しが一番下になるような順序で表示されます。 また、視認性が高まるように ANSI エスケープシーケンスを用いて装飾がされるようになりました。 次のスクリプトを実行した結果を見比べて違いを確認してみましょう。
# error.rb def a; nothing; end def b; a; end b
この変更は実験的な位置付けとされているので、もしかしたら Ruby 2.6 で廃止になるかもしれませんし、ならないかもしれません。 その点ご注意ください。
参考リンク
組み込みクラスの変更点
Kernel#yield_self
が追加された
ブロックの評価結果を返してくれる tap
が欲しいなと思っていた皆さん、お待たせいたしました。
とうとうそのためのメソッドの名前が決まり、Ruby 2.5 に追加されました。それが Kernel#yield_self
です。
参考リンク
- https://bugs.ruby-lang.org/issues/6721
- https://blog.bigbinary.com/2017/12/12/ruby-2-5-added-yield_self.html
- https://qiita.com/opt-link/items/8195f3a03859bcb10920
- https://qiita.com/stomk/items/58dce3e5e294639ab1c9
Dir.glob
に base
キーワード引数が追加された
Dir.glob
は引数で与えた glob パターンにマッチするディレクトリエントリーの配列を返してくれる便利メソッドです。
このメソッドを使って、ある glob パターンにマッチするエントリーを、特定のディレクトリの内部から探してもらうためには、File.join
で基準となるディレクトリとパターンを結合するとか、Dir.chdir
で一時的に基準ディレクトリ内部に移動してから Dir.glob
するなど、一手間加える必要がありました。
# File.join する方法 Dir.glob(File.join(target_dir, glob_pattern)) # Dir.chdir しちゃうやり方 (これは非スレッドセーフ) Dir.chdir(target_dir) { Dir.glob(glob_pattern) }
Ruby 2.5 で Dir.glob
に追加された base
キーワード引数を使うと、このような一手間が不要になります。
Dir.glob(glob_pattern, base: target_dir)
参考リンク
Dir.children
と Dir.each_child
が追加された
Dir.entries
というメソッドをご存知でしょうか。これは、引数で指定したディレクトリの直下にあるエントリーを全て含む配列を返します。
結果として得られる配列には "."
と ".."
が含まれるので、ディレクトリ内のエントリーを1つずつ処理したい場合、Dir.entries
の要素を1つずつ yield
してくれる Dir.foreach
を使って次のように書くことが多かったはずです。
Dir.foreach(target_dir) do |entry| next if entry == "." || entry == ".." process_entry(target_dir) end
Ruby 2.5 で追加された Dir.children
は Dir.entries
の配列から "."
と ".."
を除いたものを返します。
対応する Dir.each_child
は、 Dir.children
の要素を1つずつ yield
してくれるイテレータです。
これらを用いることで、 Ruby 2.5 では上のコードをより簡潔に以下のように書けるようになりました。
Dir.each_child(target_dir, &method(:process_entry))
参考リンク
Hash#transform_keys
と Hash#transform_keys!
が追加された
ActiveSupport が提供する Hash
の transform_keys
と transform_keys!
が Ruby 2.5 から組み込みで提供されるようになりました。
これらのメソッドは、Hash のキーをシンボルに統一したい場合などでよく使うんじゃないかと思います。
Ruby 2.4 で Hash#transform_values
と Hash#transform_values!
が組み込み化されているので、これで両方とも CRuby のコアで提供されるようになりましたね。どちらも提案したのは私です (ドヤァ)。
参考リンク
- https://bugs.ruby-lang.org/issues/13583
- http://api.rubyonrails.org/classes/Hash.html#method-i-transform_keys
- http://api.rubyonrails.org/classes/Hash.html#method-i-transform_keys-21
Hash#slice
が追加された
これも ActiveSupport からの輸入です。
Hash から特定のキーに該当するエントリだけを取り出して新しい Hash オブジェクトを作りたいシーンはたまにあります。
JSON や YAML をロードして作ったハッシュから一部を取り出す場合が真っ先に思いつきます。
そのような用途のために ActiveSupport は Hash#slice
を提供していました。
Ruby 2.5 からは組み込みで提供されます。
参考リンク
Struct
のサブクラスがキーワード引数での初期化に対応した
Struct.new
に keyword_init:
というオプショナルキーワード引数が追加されました。
これのデフォルト値は false
です。これに true
を指定すると、生成される Struct
のサブクラスの initialize
メソッドが、メンバフィールドの値がキーワード引数で与えられることを想定して定義されます。
Foo = Struct.new(:a, :b, keyword_init: true) Foo.new(a: 1, b: 2) #=> #<struct Foo a=1, b=2>
参考リンク
Enumerable#any?
, #all?
, #none?
, #one?
がパターンの引数渡しに対応した
Enumerable
の any?
、all?
、none?
、one?
は、レシーバ内の各要素に対して与えられたブロックを評価した結果の真偽について、それぞれ次の命題を調べます。
any?
- 1つ以上が真であるall?
- 全てが真であるnone?
- 全てが偽であるone?
- たった1つだけ真であることを判定する
Ruby 2.5 から、これらのメソッドはブロックの代わりに引数でパターンを受け取り、そのパターン p
とレシーバ内の要素 e
を ===
メソッドで比較した結果 p === e
の真偽について上記の命題の判定を行えるようになりました。
そのため、次のように、ブロックの中で単に正規表現マッチをしているような場合や、クラスオブジェクトと is_a?
をしているような場合は、よりシンプルに記述できるようになります。
# Ruby 2.4 まで strs.all? {|s| /foo/.match?(s) } nums.none? {|x| x.is_a? Complex }
# Ruby 2.5 から strs.all?(/foo/) nums.none?(Complex)
参考リンク
Integer#pow
が冪剰余に対応した
Integer#pow
が冪剰余 (Modular exponentiation) の算出に対応しました。冪剰余を求めるには、冪剰余の法を第2引数に指定します。
整数 , について、整数 を法とする冪剰余は で計算できます。計算結果は ですが、この式をそのまま評価すると や が大きい場合に の結果を求めるために大きなメモリが必要になってしまいます。冪剰余には を求めずに結果を計算できるアルゴリズムが存在し、Integer#pow
ではそのアルゴリズムを利用しているため少ないメモリ量で結果を求められます。
参考リンク
Integer
がビットマスクのテストに対応
Integer
クラスに、3つのインスタンスメソッド allbits?
、anybits?
、nobits?
が追加されました。これらはビットマスクのテストをします。次の表に意味をまとめました。
式 | 意味 |
---|---|
n.allbits?(m) |
(n & m) == m |
n.anybits?(m) |
(n & m) != 0 |
n.nobits?(m) |
(n & m) == 0 |
参考リンク
標準添付ライブラリの変更点
もう require "pp"
しなくて良い
Kernel#pp
メソッドが、明示的に require "pp"
をせずに使えるようになりました。
しかしながら、pp
が組み込みで提供されるようになった訳ではありません。
どうなっているかというと、require "pp"
して自分自身を本物の pp
で上書きしてしまう pp
メソッドが組み込みで提供されているのです。
そのため、pp
を一切使用しない場合には全くオーバーヘッドを発生させず、pp
を使いたいシーンでは勝手に定義をロードしてくれる便利な状態になりました。
参考リンク
coverage ライブラリが条件分岐とメソッド呼び出しのカバレッジの計測に対応した
Ruby 2.5 で、coverage ライブラリが分岐カバレッジとメソッド・カバレッジの計測に対応しました。 ちなみに、これまでは行カバレッジと呼ばれる種類のカバレッジのみ対応していました。
分岐カバレッジに対応したことで、条件分岐の分岐先ごとにカバレッジを計測できるようになりました。 これまでも、以下のように分岐先を異なる行に分けて記述していれば行カバレッジで正しくカバレッジを計測できていました。
if rand(2).odd? p :odd else p :even end
しかし、次のように1行にまとめて記述されていると、行カバレッジでは分岐先ごとのカバレッジは計測できません。
if rand(2).odd? then p :odd else p :even end
分岐カバレッジはこのような場合でも正しく分岐先ごとのカバレッジを計測できます。
分岐カバレッジによってカバレッジ計測が精確になったものは if
式だけでなく、
unless
式、case
式、while
式、until
式、
後置 if
修飾子、後置 unless
修飾子、後置 while
修飾子、後置 until
修飾子、三項演算子 (?:
)、
ぼっち演算子こと safe navigation operator (&.
) によるメソッド呼び出しが該当します。
メソッド・カバレッジは、メソッド単位でのカバレッジです。 カバレッジ計測としては行カバレッジよりも荒い指標ですが、メソッドが使用されたかどうか、 何回呼び出されたかを確認するときに便利です。
参考リンク
Gemify された標準ライブラリたち
Ruby 2.5 では、次の標準添付ライブラリがデフォルト gem *3になりました。
- cmath
- csv
- date
- dbm
- etc
- fcntl
- fiddle
- fileutils
- gdbm
- ipaddr
- scanf
- sdbm
- stringio
- strscan
- webrick
- zlib
パフォーマンス改善
trace
命令の削除で全体的に 5〜10% 高速化された
Ruby 2.5 では trace
命令がバイトコードから削除されました。その結果、全体的に実行速度が 5〜10% 高速化されています。
trace
命令は TracePoint
のために導入された命令ですが、Ruby 2.5 では動的に命令列を変更する方式が採用されたため trace
命令は不要になりました。
この変更によって、TracePoint
を利用しない場合はもちろんですが、TracePoint
を利用する場合も若干高速化されたようです。
参考リンク
ブロックパラメータによるブロック渡しが高速化された
ブロックパラメータによるブロック渡しとは以下のようなコードのことです。
def block_pass_through(&b) other_method(&b) end
このように、ブロックを引数で受け取り、それを他のメソッドに受け流しする場合、Ruby 2.5 では Proc
オブジェクトの生成を後回しにすることで Ruby 2.4 までと比べて約3倍高速化されました。
参考リンク
ERB のコード生成が高速化された
ERB がテンプレートから実行可能な Ruby プログラムを生成する速度が Ruby 2.4 と比べて約2倍高速化されました。
参考リンク
- https://speakerdeck.com/k0kubun/cruby-and-erb-optimization-at-td-hackathon?slide=53
- https://github.com/ruby/ruby/pull/1612
- https://bugs.ruby-lang.org/issues/12074
- https://github.com/ruby/ruby/pull/1147
- https://github.com/ruby/ruby/commit/0e6776d7526ece11c707fd67cc57313144fc86a9
- https://github.com/ruby/ruby/commit/a421fe63249b08228c1193597137467b0c9eeedf
文字列への長い文字列の埋め込みが高速化された
長い文字列を別の文字列へ埋め込む処理が高速化されました。埋め込み処理の前に必要なメモリを割り当てておくことで、埋め込み処理中に発生するリサイズを抑制し、以下の例では 72% 高速化されています。
参考リンク
組み込みクラスのメソッドが高速化された
組み込みクラスの様々なメソッドが高速化されました。代表例は以下の通りです。
Array#concat
Enumerable#sort_by
String#concat
String#index
Time#+
まとめ
もうすぐリリースされる Ruby 2.5.0 の変更点の中から、インパクトが大きそうなものを選んで参考リンクを添えて紹介しました。 ここで紹介しなかった変更も沢山あります。NEWS の内容と、リリースされたら公開されるリリースノートも必ず見てください。
みなさんが新しい Ruby とともに楽しく年末年始を過ごせるよう願っています。 ご精読ありがとうございました。