Ruby

サンプルコードでわかる!Ruby 2.5の主な新機能と変更点 Part 2

はじめに

Rubyは毎年12月25日にアップデートされます。
Ruby 2.5のpreview1がリリースされた際に新機能をまとめた記事を書いたのですが、2017年12月14日にリリースされたrc1ではさらに多くの新機能が導入されていました。

これをそのままpreview1の記事に追記すると、記事のボリュームがかなり大きくなるため、rc1で導入された新機能は今回別記事(Part 2)として公開することにします。

ちなみにPart 1の記事はこちらです。

サンプルコードでわかる!Ruby 2.5の主な新機能と変更点 Part 1 - Qiita

本記事の情報源

本記事は以下のNEWSページに掲載されている情報から、個人的に注目したい新機能(なおかつPart 1に載っていないもの)をピックアップしたものです。

NEWS(commit: 7361587)

この記事に掲載していない変更点もあるので、詳細はNEWSページをご覧ください。

また、説明している内容に間違いがあれば、コメントや編集リクエスト等で優しく指摘してやってください。
ここで紹介していない新機能に関する編集リクエストも大歓迎です!

動作確認したRubyのバージョン

本記事は以下の環境で実行した結果を記載しています。

$ ruby -v
ruby 2.5.0dev (2017-12-24 trunk 61431) [x86_64-darwin16]

ご覧のとおり確認したのは12月24日時点のdev版なので、Ruby 2.5が正式リリースされたらあらためて内容をチェックしようと思います。

2017.12.26追記: 正式リリース版でも動作確認しました

正式リリースされたRuby 2.5.0でも動作確認を行いました。

$ ruby -v
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin16]

また、Ruby 2.5.0の最終的なNEWSページはこちらになります。

コード例

本記事のコード例は以下のGitHubリポジトリに置いています。

https://github.com/JunichiIto/ruby-2-5-sandbox

それでは以下が本編です!

文字列やファイルの読み書きに関する新機能や変更点

String#start_withメソッドに正規表現が渡せるようになった

Ruby 2.5ではString#start_withメソッドに正規表現が渡せるようになりました。

s = '123abc'
s.start_with?(/\d+/)  #=> true
s.start_with?(/9\d+/) #=> false

IO#writeStringIO#writeに複数の引数を渡せるようになった

Ruby 2.5ではIO#writeStringIO#writeに複数の引数を渡せるようになりました。

path = '/tmp/sample.txt'
File.open(path, 'w') do |f|
  # writeメソッドに複数の引数を渡す
  f.write 'abc', '1234'
end
File.read(path) #=> "abc1234"
require "stringio"
a = StringIO.new("hoge", 'r+')
# writeメソッドに複数の引数を渡す
a.write "abc", "1234"
a.string #=> "abc1234"

Pathnameクラスにインスタンスメソッドのglobが追加された

Ruby 2.5ではPathnameクラスにインスタンスメソッドのglobが追加されました。これにより、「Railsアプリのspecディレクトリ以下にある全rbファイルを、Pathnameオブジェクトの配列として取得する」というようなコードが簡潔に書けるようになります。

# Ruby 2.4の場合
pathnames = Pathname.glob(Rails.root.join('spec/**/*.rb'))

# Ruby 2.5の場合
pathnames = Rails.root.glob('spec/**/*.rb')

配列やハッシュに関する新機能や変更点

Railsでお馴染みのHash#sliceがRuby標準のメソッドになった

これまでRails(ActiveSupport)の拡張機能だったHash#sliceメソッドがRuby標準のメソッドになりました。sliceメソッドは指定したキーに合致するキーと要素からなる、新しいハッシュを返します。

hash = { a: 'Alice', b: 'Bob', c: 'Carol' }
hash.slice(:a, :c) #=> { a: 'Alice', c: 'Carol' }

ただし、slice!except/except!は今回導入されなかったようです。

Enumerable#any?/all?/none?/one?にパターンオブジェクトを渡せるようになった

Ruby 2.5ではEnumerableのany?/all?/none?/one?メソッドにパターンオブジェクトが渡せるようになりました。言葉ではちょっと説明しづらいので、先にコードを見てもらった方が分かりやすいと思います。

arr = ['abc', 123]

# Ruby 2.4の場合
# 整数が1つでも含まれるか?
arr.any? { |obj| Integer === obj } #=> true
# 実数が一つでも含まれるか?
arr.any? { |obj| Float === obj }   #=> false

# Ruby 2.5の場合
arr.any?(Integer) #=> true
arr.any?(Float)   #=> false

arr = ['123-4567', '789-0123']

# Ruby 2.4の場合
# 全要素が/\d+-\d+/にマッチするか?
arr.all? { |s| /\d+-\d+/ === s } #=> true
# 全要素が/123-\d+/にマッチするか?
arr.all? { |s| /123-\d+/ === s } #=> false

# Ruby 2.5の場合
arr.all?(/\d+-\d+/) #=> true
arr.all?(/123-\d+/) #=> false

# none?やone?も同じような考え方で引数を渡せる

つまり、メソッドの引数に渡されたオブジェクトと配列(正確にはEnumerableモジュールをincludeしているオブジェクト)の各要素を===で比較し、その戻り値をany?/all?/none?/one?の判定に使う、という挙動になります。

ちなみに===はcase文などで内部的に使用される演算子です。詳しくは公式ドキュメントを参照してください。

例外やデバッグに関する新機能や変更点

バックトレースの表示順が逆になった

(この内容はPart 1でも書きましたが、若干内容が変わっている点もあるのであらためてここでまとめます)

Ruby 2.5ではエラー発生時のバックトレースの出力順が逆になっています。

たとえば次のような例外が発生するスクリプトがあったとします。

def method_1
  method_2
end

def method_2
  # ZeroDivisionErrorを発生させる
  1 / 0
end

method_1

Ruby 2.4までは次のようにバックトレースが出力されていました。

$ ruby ./test/error_example.rb 
./test/error_example.rb:7:in `/': divided by 0 (ZeroDivisionError)
  from ./test/error_example.rb:7:in `method_2'
  from ./test/error_example.rb:2:in `method_1'
  from ./test/error_example.rb:10:in `<main>'

Ruby 2.5ではこれが逆順で表示されます。

$ ruby ./test/error_example.rb 
Traceback (most recent call last):
        3: from ./test/error_example.rb:10:in `<main>'
        2: from ./test/error_example.rb:2:in `method_1'
        1: from ./test/error_example.rb:7:in `method_2'
./test/error_example.rb:7:in `/': divided by 0 (ZeroDivisionError)

元のIssueを読むと、「バックトレースが長大になるとき、ターミナル上で上スクロールせずにエラーメッセージが確認できる」「上から下に実行過程が読める」というメリットがある、とのことです。

ただし、今のところ逆順に表示されるのは「組み込み定数であるSTDERRが変更されておらず、なおかつ、tty(標準入出力となっている端末デバイス)に出力する場合」と「IRBで実行した場合」になっています。

そのため、上記の条件に合致しない場合は今までどおりの出力順になっています。

また、例外オブジェクトのbacktraceで返される配列もこれまでどおりの順番です。

def method_1
  method_2
rescue => e
  # Ruby 2.4も2.5も中身の順序は同じ
  puts e.backtrace
end

この仕様変更は「実験中(experimental)」としてリリースされます。Ruby 2.6以降ではまた扱いが変わるかもしれません。

例外クラス名、例外メッセージ、バックトレースが1つの文字列として返るException#full_message

Ruby 2.5では例外クラス名、例外メッセージ、バックトレースが1つの文字列として返るException#full_messageメソッドが追加されました。

begin
  1 / 0
rescue => e
  puts e.full_message
end
#=> Traceback (most recent call last):
#=>   1: from full_msg.rb:2:in `<main>'
#=> full_msg.rb:2:in `/': divided by 0 (ZeroDivisionError)

さくっとエラー内容をログに残したり、デバッグ時にエラーの詳細を確認したりするのに便利そうです。

freezeしたオブジェクトに破壊的変更を加えようとするとFrozenErrorが発生するようになった

freezeしたオブジェクトに破壊的変更を加えようとするとRuby 2.4まではRuntimeErrorが発生していました。

s = 'abc'.freeze
s.upcase! #=> RuntimeError: can't modify frozen String

Ruby 2.5ではRuntimeErrorではなく、FrozenErrorが発生します。

s.upcase! #=> FrozenError: can't modify frozen String

なお、FrozenErrorはRuntimeErrorクラスのサブクラスです。

FrozenError.superclass #=> RuntimeError

requireなしでppメソッドが使えるようになった

ppメソッドを使う場合、これまではrequire 'pp'が必要でしたが、Ruby 2.5ではrequireなしで使えます。

ちなみにppは"pretty print"の略で、複雑なオブジェクトを表示したりする場合にpメソッドよりも読みやすく出力されます。

data = [false, 42, %w{fourty two}, {:now => Time.now, :class => Time.now.class, :distance => 42e42}]

p data
#=> [false, 42, ["fourty", "two"], {:now=>2017-12-24 06:21:05 +0900, :class=>Time, :distance=>4.2e+43}]

# Ruby 2.5ではrequire 'pp'が不要
pp data
#=> [false,
#    42,
#    ["fourty", "two"],
#    {:now=>2017-12-24 06:21:05 +0900, :class=>Time, :distance=>4.2e+43}]

binding.irbの機能改善

Ruby 2.4ではプログラムの実行中にirbが開けるbinding.irbメソッドが追加されました。

Ruby 2.5ではこのbinding.irbに関して以下のような機能改善が行われました。

  • require 'irb'を明示的に書かなくても自動的にirbライブラリが読み込まれる
  • 実行が停止した行の周辺コードが表示される

以下はbinding.irbでコードを停止したときの表示例です。

$ ruby ./test/binding_irb_sample.rb 

From: ./test/binding_irb_sample.rb @ line 6 :

     1: class Test
     2:   attr_accessor :x, :y, :z
     3:   def initialize(x, y, z)
     4:     @x = x
     5:     @y = y
 =>  6:     binding.irb
     7:     @z = z
     8:   end
     9: end
    10: 
    11: Test.new(1, 2, 3)

irb(#<Test:0x00007f9f5f02fe08>):001:0> 

パフォーマンス改善

メソッドの引数に&blockを書いた場合の速度が改善された

Ruby 2.4まではメソッドの引数に&blockを書いたときと書かないときで速度に違いがありました。

# block を引数で受け取って、call で呼ぶ => 遅い
def block_call_with_block_arg(&block)
  block.call
end

# 引数で受け取った block を捨てて yield で呼ぶ => これも遅い
def yield_with_block_arg(&block)
  yield
end

# block を引数で受け取らずに yield で呼ぶ => 速い
def yield_without_block_arg
  yield
end

Ruby 2.5では上記コードの2つめのパターンが、一番下のパターンと同じぐらい速くなりました。

詳しい内容は以下のブログ記事をご覧ください(上記のサンプルコードもこちらのブログ記事から引用させてもらいました)。

Ruby 2.5 は引数に &block を書いても速い!!! - onk.ninja

ERBテンプレートからのコード生成が2倍速くなった

NEWSには"ERB now generates code from a template which runs 2 times faster than Ruby 2.4"とあるので、ERBテンプレートからのコード生成がRuby 2.4と比較して2倍速くなったそうです。

その他の新機能や変更点

Bundlerの標準ライブラリ化はいったん延期

もともとRuby 2.5ではBundlerが標準ライブラリに取り込まれることになっていましたが、リリース直前に大きな問題が見つかったため、この対応は延期されることになったようです。

キーワード引数でStructを初期化できるようになった

Structで作ったクラスを初期化する場合、これまでは必ず引数の順番を覚えておく必要がありました。

# Ruby 2.4の場合
OldPoint = Struct.new(:x, :y)
# 必ずx, yの順で引数を指定する
p1 = OldPoint.new(1, 2)
p1.x #=> 1
p1.y #=> 2

Ruby 2.5ではStruct.newkeyword_initというオプションを指定できます。これをtrueにすると、キーワード引数で初期化できます。

# キーワード引数で初期化可能なオプションを付けてクラスを作る
NewPoint = Struct.new(:x, :y, keyword_init: true)
# キーワード引数で初期化できる(キーワード引数なので順番は変わってもかまわない)
p2 = NewPoint.new(y: 20, x: 10)
p2.x #=> 10
p2.y #=> 20

Methodオブジェクトを===で呼び出せるようになった

Ruby 2.5ではMethodオブジェクトを===で呼び出せるようになりました。

def hello(name)
  "Hello, #{name}!"
end

method(:hello) === 'Alice' #=> "Hello, Alice!"

これにより、case文のwhen節に直接Methodオブジェクトを書いたりできるようになります。

def adult?(age)
  age > 20
end

def child?(age)
  age < 20
end

def adult_or_child(age)
  case age
  when method(:adult?)
    '大人です'
  when method(:child?)
    '子どもです'
  else
    'はたちです'
  end
end

adult_or_child(21) #=> 大人です
adult_or_child(19) #=> 子どもです
adult_or_child(20) #=> はたちです

attr_accessordefine_methodがpublicメソッドになった

Ruby 2.5ではModuleクラスの以下のメソッドがpublicメソッドになりました。

  • attr
  • attr_accessor
  • attr_reader
  • attr_writer
  • define_method
  • alias_method
  • undef_method
  • remove_method

これにより、メタプログラミングが少し楽に書けるようになります。

user_class = Class.new
user_class.attr_accessor :name
user_class.define_method(:hello, -> { "Hello, I am #{name}." })

user = user_class.new
user.name = 'Alice'

user.name #=> "Alice"
user.hello #=> "Hello, I am Alice."

英数字のみで構成されるランダムな文字列を生成するSecureRandom.alphanumeric

Ruby 2.5では英数字のみで構成されるランダムな文字列を生成するSecureRandom.alphanumericメソッドが追加されました。

require 'securerandom'
SecureRandom.alphanumeric #=> "cr12XFfO3FbtAHB1"
SecureRandom.alphanumeric #=> "Rg96LmqVWQ2DoNzu"

Thread.report_on_exceptionのデフォルト値がtrueになった

Ruby 2.4では別スレッドの例外を報告するかどうかを決めるThread.report_on_exceptionフラグが導入されました。

Ruby 2.4では明示的にtrueをセットしないとこの機能が有効になりませんでしたが、Ruby 2.5ではデフォルトでtrueになっています。

# Ruby 2.5ではtrueがデフォルト値
Thread.report_on_exception #=> true

まとめ

というわけで、Ruby 2.5の新機能をあれこれ紹介してみました。
Rubyコミッタの方々をはじめ、Ruby 2.5の開発に携わってくださった開発者のみなさまに感謝します。

Part 1の記事とあわせて読んで、Ruby 2.5を使ったRubyプログラミングを楽しんでください!