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

  • 154
    いいね
  • 4
    コメント

はじめに

Rubyは毎年12月25日にアップデートされます。
今年はまだpreview版がリリースされていませんが(2017年10月10日時点)、今年もそろそろリリースの日が近づいてきました。
Ruby 2.5については2017年10月10日にpreview1がリリースされました。

Ruby 2.5.0-preview1 Released

そこでこの記事ではこの2.5.0-preview1を参考にして、おそらくこんな感じでリリースされるであろうRuby 2.5の新機能や変更点をまとめてみました。

本記事の情報源

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

NEWS(commit: 6c29f23)

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

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

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

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

$ ruby -v
ruby 2.5.0preview1 (2017-10-10 trunk 60153) [x86_64-darwin16]

コード例

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

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

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

言語仕様上の変更点

do/endブロック内でbegin/endなしのrescue/else/ensureが書けるようになった

Ruby2.5ではブロック内でbegin/endなしのrescue/else/ensureが書けるようになりました。
これによりネストの深さと行数を節約できます。

# Ruby 2.4
[1].each do |n|
  begin
    n / 0
  rescue
    # rescue
  else
    # else
  ensure
    # ensure
  end
end

# Ruby 2.5
[1].each do |n|
  n / 0
rescue
  # rescue
else
  # else
ensure
  # ensure
end

ただし、ブロックを{}で書いた場合は構文エラーになります。

[1].each { |n|
  n / 0
rescue
  # rescue
else
  # else
ensure
  # ensure
}
#=> SyntaxError: (irb):3: syntax error, unexpected keyword_rescue, expecting '}'
#     rescue
#     ^~~~~~

do/endと{}で挙動が変わるの?と思ってしまったんですが、むしろ{}を除外することで提案が通ったそうです。

トップレベルの定数探索のルールが変更された

下のような3つのクラスがあったとします。

# Ruby 2.4
class Item; end
class Staff; end
class ItemsController; end

さらにこの状態から、次のクラスを参照します。

Staff::ItemsController

ぱっと見、「こんなクラスは定義してないよ!」と思うかもしれませんが、Ruby 2.4では(警告付きで)ItemsControllerを参照したことになります。

# Ruby 2.4
Staff::ItemsController
#=> warning: toplevel constant ItemsController referenced by Staff::ItemsController
#=> ItemsController

なぜなら、トップレベルのクラス定義はObjectクラス以下にクラスが定義され、さらにRubyはStaffクラス、Objectクラスと順に継承関係をさかのぼってItemsControllerが定義されていないか探しに行っていたためです。

しかし、この仕様はプログラマが本来求めているものと異なるクラスが返される恐れがあり、わかりづらい不具合の原因になっていました。
こちらの記事で詳しく説明しています。)

そこでRuby 2.5では、継承関係をさかのぼらなくなりました(Staffクラス以下にItemsControllerが定義されていなければエラー)。

# Ruby 2.5
Staff::ItemsController
#=> NameError: uninitialized constant Staff::ItemsController
#   Did you mean?  ItemsController

ItemsControllerを参照するためには、以下のいずれかの方法をとることになります(この書き方はいずれもRuby 2.4でも有効です)。

ItemsController
Object::ItemsController
::ItemsController

refinementsでto_sを書き換えたときに、文字列の式展開でもrefinements側のto_sが使われるようになった

次のようなrefinementsを使ったクラス定義があったとします。

class A
end

module B
  refine A do
    def to_s
      'b'
    end
  end
end

class C
  using B

  def initialize
    @a = A.new
  end

  def c1
    @a.to_s
  end

  def c2
    "#{@a}"
  end
end

さらにクラスCのc1メソッドとc2メソッドを次のように呼び出します。

C.new.c1
C.new.c2

明示的にto_sメソッドを呼びだしているc1メソッドはもちろん、式展開で暗黙的にto_sが使われるc2メソッドもどちらも同じ結果になる(module Bのrefinementsが有効になっている)と思いますが、Ruby 2.4ではこのようになっていました。

# Ruby 2.4
C.new.c1 #=> "b"
C.new.c2 #=> "#<A:0x00007f870a852d50>"

ごらんのとおり、c2メソッド(式展開を使った場合)はrefinementsが有効になっていません。
ですが、Ruby 2.5ではどちらもrefinementsが有効になりました。

# Ruby 2.5
C.new.c1 #=> "b"
C.new.c2 #=> "b"

標準ライブラリ関連の変更点

Bundlerが標準ライブラリに取り込まれた

Bundlerが標準ライブラリに取り込まれました。
なので、gem install bundlerなしでBundlerが使えるようになります。

この記事の執筆時点ではBundler 1.15.4がインストールされるようです。

$ ruby -v
ruby 2.5.0preview1 (2017-10-10 trunk 60153) [x86_64-darwin16]
$ bundler -v
Bundler version 1.15.4

ERBのローカル変数をHashで渡せる ERB#result_with_hash メソッド

これまでERBでテンプレート内で使われるローカル変数を渡すには、以下のようにbindingを渡したり、あるいは渡す変数を絞るためにstructを定義する必要がありました。

require 'erb'
require 'ostruct'

namespace = OpenStruct.new(a: 2, b: 3)
template = 'Result: <%= a * b %>'
ERB.new(template).result(namespace.instance_eval { binding }) #=> "Result: 6"

これが、ERB#result_with_hashの導入により、最低限の変数だけ渡すのが以下のように簡単に書けるようになりました。

require 'erb'

ERB.new('Result: <%= a * b %>').result_with_hash(a: 2, b: 3) #=> "Result: 6"

実験中の変更点

バックトレースの表示順が逆になる?

これはあくまで「実験段階」の変更点ですが、エラー発生時のバックトレースの出力順が逆になっています。

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

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
  begin
    method_2
  rescue => e
    # Ruby 2.4も2.5も中身の順序は同じ
    puts e.backtrace
  end
end

ただし、繰り返しになりますが、これはまだ「実験中」の仕様変更です。
フィードバックを集めてからこの変更を適用するかどうかを決定するとのことです(参考)。

僕個人の意見としては「今さら導入するにはユーザーへのインパクトが大きそうな割に、そこまで嬉しいメリットもないのでどちらかといえば反対」ですね。

オブジェクト全般の新機能

ブロックの実行結果がそのまま戻り値になるyield_self

Ruby 2.5ではレシーバがブロックの引数になり、ブロックの結果がそのまま戻り値になるyield_selfが追加されました。
Kernelモジュールのメソッドなので、すべてのオブジェクト(BasicObjectを除く)で使用できます。

# レシーバ(= 2)を受け取り、ブロックの戻り値(= 2 * 10)がメソッド全体の戻り値になる
2.yield_self { |n| n * 10 } #=> 20

以下は配列に含まれる文字列をカンマで連結し、さらにそれを丸括弧で囲むコード例です。

names = ['Alice', 'Bob']
names.join(', ').yield_self { |s| "(#{s})" } #=> "(Alice, Bob)"

ちなみに、yield_selfについてはこちらの記事でも紹介されています。

Ruby2.5で導入されるyield_selfについて - Qiita

文字列/正規表現に関する新機能

接頭辞や接尾辞を削除するdelete_prefix/delete_suffix

Ruby 2.5では文字列から接頭辞や接尾辞を削除するdelete_prefixdelete_suffixが追加されました。

'invisible'.delete_prefix('in') #=> "visible"
'pink'.delete_prefix('in') #=> "pink"

'worked'.delete_suffix('ed') #=> "work"
'medical'.delete_suffix('ed') #=> "medical"

casecmp/casecmp?に文字列以外の引数を渡したときに例外ではなくnilを返すようになった

Ruby 2.5ではcasecmp/casecmp?メソッドに文字列以外の引数(数値など)を渡したときにnilを返すようになりました。

# Ruby 2.4
'abc'.casecmp(1)  #=> TypeError
'abc'.casecmp?(1) #=> TypeError

# Ruby 2.5
'abc'.casecmp(1)  #=> nil
'abc'.casecmp?(1) #=> nil

シンボルでは以前からnilを返していたいので、それに挙動を合わせたようです。

# Ruby 2.4, 2.5
:abc.casecmp(1)  #=> nil
:abc.casecmp?(1) #=> nil

正規表現で非包含オペレータが使えるようになった(Ruby 2.4.1以降)

これはRuby 2.5ではなく、Ruby 2.4.1からの新機能ですが、正規表現エンジンが鬼雲6.1.1にアップデートされ、非包含オペレータ((?~))が使えるようになりました。

以下は非包含オペレータを使って、DEBUGとINFOを含まない行を抜き出すコード例です。

text = <<LOG
10:00 [INFO] Lorem ipsum dolor sit amet
10:10 [WARN] Lorem ipsum dolor sit amet
10:20 [INFO] Lorem ipsum dolor sit amet
10:25 [DEBUG] Lorem ipsum dolor sit amet
10:30 [ERROR] Lorem ipsum dolor sit amet
10:40 [INFO] Lorem ipsum dolor sit amet
LOG

puts text.scan(/^(?~DEBUG|INFO)$/)
#=> 10:10 [WARN] Lorem ipsum dolor sit amet
#   10:30 [ERROR] Lorem ipsum dolor sit amet

非包含オペレータの詳細については以下の記事を参照してください。

Unicode 10をサポートするようになった

Ruby 2.5ではUnicode 10をサポートするようになりました。

これにより、たとえば正規表現で変体仮名を表すIn_Kana_Extended_Aプロパティ(Unicode 10で追加されたプロパティ)を使えたりするようになります。

"A\u{1B10A}B".match?(/\p{In_Kana_Extended_A}/) #=> true

ちなみに\u{1B10A}というのはこんな文字です。(大半のブラウザでは表示できないはずです)

Screen Shot 2017-10-10 at 7.40.58.png
http://www.unicode.org/charts/PDF/Unicode-10.0/U100-1B100.pdf

配列に関する新機能

unshift/pushのエイリアスメソッドとしてprepend/appendが追加された

Ruby 2.5ではunshift/pushのエイリアスメソッドとしてprepend/appendが追加されました。

array = [3, 4]
array.prepend(1, 2) #=> [1, 2, 3, 4]
array               #=> [1, 2, 3, 4]

array = [1, 2]
array.append(3, 4)  #=> [1, 2, 3, 4]
array               #=> [1, 2, 3, 4]

個人的にはunshift/pushよりも直感的に理解しやすいメソッド名だなと思います :smiley:

ハッシュに関する新機能

キーを特定のルールで変換するtransform_keys/transform_keys!

Ruby 2.5ではハッシュのキーを特定のルールで変換するtransform_keysが追加されました。

hash = { a: 1, b: 2 }
hash.transform_keys { |k| k.to_s }
#=> { 'a' => 1, 'b' => 2 }

transform_keys!はレシーバのハッシュ自身を変更させます(破壊的メソッド)。

hash = { a: 1, b: 2 }
hash.transform_keys! { |k| k.to_s }
#=> { 'a' => 1, 'b' => 2 }

hash
#=> { 'a' => 1, 'b' => 2 }

ちなみにRuby 2.4ではハッシュの値を変換するtransform_valuesメソッドが追加されていました。

hash = {a: 1, b: 2, c: 3}
hash.transform_values {|v| v ** 2 } #=> {a: 1, b: 4, c: 9}

KeyErrorにreceiverメソッドとkeyメソッドが追加された

Ruby 2.5ではfetchメソッドなどでキーが見つからなかったときに発生するKeyErrorにreceiverメソッドとkeyメソッドが追加されました。

receiverはエラーが起きたハッシュ自身を、keyは見つからなかったキーを返します。

begin
  h = {foo: 1, bar: 2}
  h.fetch(:bax)
rescue KeyError => e
  e.receiver #=> {:foo=>1, :bar=>2}
  e.key      #=> :bax
end

数値に関する新機能

round/floor/ceil/truncateでレシーバをFloatに変換しなくなった

Ruby 2.4まではroundメソッドを使うと、レシーバをFloatに変換してから四捨五入をしていました。

2.round(2) #=> 2.0

そのため、大きな数になると丸め誤差の関係で数学的におかしな結果が返ってきていました。

(10**25).round(2)      #=> 1.0e+25
(10**25).round(2).to_i #=> 10000000000000000905969664

Ruby 2.5では無理にFloatに変換しないので、数学的に正しい結果が返ります。

2.round(2)        #=> 2
(10**25).round(2) #=> 10000000000000000000000000

これはfloor/ceil/truncateについでも同様です。

(10**25).floor(2)    #=> 10000000000000000000000000
(10**25).ceil(2)     #=> 10000000000000000000000000
(10**25).truncate(2) #=> 10000000000000000000000000

参考: Feature #13420: Integer#{round,floor,ceil,truncate} should always return an integer, not a float

正しい精度で平方根を返すInteger.sqrt

roundメソッドと同様、Math.sqrtメソッドも引数をFloatとして扱うために、大きな数では精度が狂うケースがありました。

n = 10**46
# 数学的には 100000000000000000000000 が正
Math.sqrt(n).to_i #=> 99999999999999991611392

このようなケースではRuby 2.5で追加されたInteger.sqrtを使うと、正しい値が返ってきます。

n = 10**46
Integer.sqrt(n) #=> 100000000000000000000000

なお、平方根の値が小数になる場合は小数点以下が切り捨てられます。
引数が整数以外の数値であれば、最初に整数に変換されてから平方根を計算します。

Math.sqrt(3)      #=> 1.7320508075688772
Integer.sqrt(3)   #=> 1
Integer.sqrt(9.9) #=> 3

日時に関する新機能

Time#atメソッドでミリ秒/ナノ秒を指定できるようになった

Time#atメソッドでは第1引数にエポック秒を、第2引数にマイクロ秒を指定することができます。

def format_time(t)
  t.strftime('%Y-%m-%d %H:%M:%S.%N')
end

t = Time.at(1514127600, 1)
format_time(t) #=> "2017-12-25 00:00:00.000001000"

Ruby 2.5では第3引数を指定することでミリ秒やナノ秒も設定できるようになりました。

# ミリ秒
format_time Time.at(1514127600, 1, :millisecond)
#=> "2017-12-25 00:00:00.001000000"

# マイクロ秒
format_time Time.at(1514127600, 1, :usec)
#=> "2017-12-25 00:00:00.000001000"
format_time Time.at(1514127600, 1, :microsecond)
#=> "2017-12-25 00:00:00.000001000"

# ナノ秒
format_time Time.at(1514127600, 1, :nsec)
#=> "2017-12-25 00:00:00.000000001"
format_time Time.at(1514127600, 1, :nanosecond)
#=> "2017-12-25 00:00:00.000000001"

ファイル/ディレクトリ操作に関する新機能

Dir#globメソッドに起点となるディレクトリを指定できるbaseオプションが追加された

Ruby 2.5ではDir#globメソッドに起点となるディレクトリを指定できるbaseオプションが追加されました。

# ./test/dir_aディレクトリを起点とし、".rb"で終わるファイルを探す
Dir.glob('./*.rb', base: './test/dir_a')

"."や".."を返さないDir#children/each_childメソッド

Rubyには指定されたパス内のファイルエントリ名を返すDir#entriesメソッドがあります。

Dir.entries('./test/dir_a')
#=> ['.', '..', 'code_a.rb', 'text_a.txt']

ただし、上の結果を見ると分かるようにentriesメソッドでは"."や".."もファイルエントリとして返却されます。

Ruby 2.5で追加されたDir#childrenメソッドを使うと、"."や".."が含まれなくなります。

Dir.children('./test/dir_a')
#=> ['code_a.rb', 'text_a.txt']

Dir#each_childメソッドは配列ではなく、Enumeratorオブジェクトを返します。

Dir.each_child('./test/dir_a')
#=> #<Enumerator: Dir:each_child(\"./test/dir_a\")>"

Dir.each_child('./test/dir_a').to_a
#=> ['code_a.rb', 'text_a.txt']

Setクラスに関する新機能

to_sがinspectのエイリアスメソッドになった

Ruby 2.5ではSetクラスのto_sメソッドがinspectメソッドのエイリアスメソッドになりました。
これにより、要素の情報が表示されるようになります。

require 'set'

s1 = Set.new
s1 << 'tic' << 'tac'

# Ruby 2.4
s1.to_s #=> #<Set:0x00007f83b98357e0>

# Ruby 2.5
s1.to_s #=> #<Set: {"tic", "tac"}>

===がinclude?のエイリアスメソッドになった

Ruby 2.5ではSetクラスの===include?のエイリアスメソッドになりました。

require 'set'

Set[1, 2, 3].include?(2) #=> true
Set[1, 2, 3].include?(5) #=> false

Set[1, 2, 3] === 2 #=> true
Set[1, 2, 3] === 5 #=> false

Threadクラスに関する新機能

スレッドに指定したキーのデータが格納されていなければデフォルト値を返すThread#fetchメソッド

RubyではThread#[]/[]=を使ってスレッド固有のデータを読み書きすることができます。

Thread.current[:foo] = 'bar'
Thread.current[:foo] #=> "bar"

Ruby 2.5ではThread#fetchメソッドが追加され、Hash#fetchメソッドと同じように指定されたキーが見つからないときのデフォルト値を指定することができます。

Thread.current[:foo] = 'bar'
Thread.current.fetch(:foo, 'baz')  #=> "bar"
Thread.current.fetch(:hoge, 'baz') #=> "baz"

# 第2引数を指定しない場合はキーが見つからないとエラーになる
Thread.current.fetch(:foo)  #=> "bar"
Thread.current.fetch(:hoge) #=> KeyError: key not found: hoge

まとめ

というわけで、本記事ではRuby 2.5の新機能や変更点をまとめてみました。

do/endブロックの中にbegin/endなしでrescueが書ける点や、定数探索のルールが変わって予期せぬ不具合が発生しにくくなった点は日常的な開発で嬉しいポイントだと思います。

一方で、バックトレースの並びが逆順になるのは「うーん、ちょっと」という気がしました。
ただ、これはまだ「実験中」の仕様変更ですので、フィードバック次第では従来のままになる可能性もあります。

その他にもいろいろと興味深い新機能が追加されています。
yield_selfなんかはこれから面白い使い方を研究していきたくなるメソッドですね。

さあ、みなさんもぜひRuby 2.5の新機能を試してみてください!

あわせて読みたい

Ruby 2.3、2.4の新機能は以下の記事にまとめてあります。
こちらもあわせてどうぞ。