Ruby 2.5

この記事は Ruby 2.5.0 preview1 時点のものです。Ruby 2.5 リリース版については http://tmtms.hatenablog.com/entry/2017/12/30/ruby25 を見てください。


Rubyは毎年クリスマスにバージョンアップされます。

今年も問題なければ12/25にRuby 2.5が出ると思います。

https://docs.ruby-lang.org/en/trunk/NEWS.html を元に変更内容を調べてみました。



言語仕様

トップレベル定数参照

class Foo
end
class Bar
end

# Ruby 2.4
Foo::Bar  #=> Bar

# Ruby 2.5
Foo::Bar  #=> NameError 例外

2.5 の方が自然ですね。

rescue/else/ensure 節

def foo
  begin
     ...
  rescue
     ...
  end
end

これは従来から次のように begin を省略できましたが、

def foo
  ...
rescue
  ...
end

2.5からはdo〜endブロックでも省略できます。

array.each do
  begin
    ...
  rescue
    ...
  end
end

↓

array.each do
  ...
rescue
  ...
end

簡潔に書けて良いです。

文字列内式のRefinement

class A
  def to_s
    "a"
  end
end

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

class C
  using B
  def x
    A.new.to_s  #=> "b"
    "#{A.new}"  #=> "b" 2.4 では "a"
  end
end

Unicode バージョン 10.0.0

Ruby 2.4 は 9.0.0

Unicode 9.0.0 と 10.0.0 の違いはわかってませんが…。


組み込みライブラリ

Array#append, #prepend 追加

append, prepend は push, unshift と同じなんですが、同じことをするにも複数の名前があるってのがRubyっぽいですね。

a = [1, 2, 3]
a.append 4
a #=> [1, 2, 3, 4]
a.prepend 0
a #=> [0, 1, 2, 3, 4]

Dir.children, Dir.each_child 追加

ディレクトリ内のエントリを ., .. を除いて返すメソッドが追加されました。

Dir.entries("/tmp/x")  #=> ["..", "abc", "."]
Dir.children("/tmp/x") #=> ["abc"]
Dir.foreach("/tmp/x"){...}    # 「.」「..」を含む
Dir.each_child("/tmp/x"){...} # 「.」「..」を含まない

Dir.glob :base オプション追加

Dir.glob にカレントディレクトリの代わりのパスを指定できるオプションが追加されました。

Dir.glob("/tmp/x/*")           #=> ["/tmp/x/abc"]

Dir.glob("*", base: "/tmp/x")  #=> ["abc"]

Hash#transform_keys, transform_keys! 追加

ハッシュ中の値を変換する transform_values メソッドは 2.4 からありましたが、キーを変更する transform_keys メソッドが追加されました。

{a: 1, b: 2, c: 3}.transform_keys(&:upcase)
#=> {A: 1, B: 2, C: 3}

{a: 1, b: 2, c: 3}.transform_values{|v| v*2}
#=> {a: 2, b: 4, c: 6}  ←これは 2.4 から

IO.pread, IO.pwrite 追加

ファイルの特定の位置から read, write するメソッド追加。POSIXの同名システムコールと同じ。 ファイルポインタは変更されません。

File.open("/tmp/x/abc") do |f|
  f.read(5)       #=> "abcde"
  f.pread(10, 3)  #=> "defghijklm"
  f.read(5)       #=> "fghij"
end

Integer.sqrt 追加

平方根を整数で返す。

Integer.sqrt(9)  #=> 3
Integer.sqrt(15) #=> 3
Integer.sqrt(16) #=> 4

Integer#round, floor, ceil, truncate が整数を返す

# Ruby 2.4
123.round(1)  #=> 123.0
123.round(-1) #=> 120

# Ruby 2.5
123.round(1)  #=> 123
123.round(-1) #=> 120

Numeric: coerce内の例外

数値とオブジェクトを比較した時に、coerce メソッドが呼ばれてるんですが、その際に発生した例外が ArgumentError となって隠蔽されてしまっていたのですが、そのまま例外が上がるようになりました。

class A
  def coerce(other)
    raise 'hoge'
  end
end

# Ruby 2.4
1 < A.new
#=> in `<': comparison of Integer with A failed (ArgumentError)

# Ruby 2.5
1 < A.new
#=> in `coerce': hoge (RuntimeError)

Range: <=>内の例外

これも同上です。

class A
  def <=>(x)
    raise 'hoge'
  end
end

# Ruby 2.4
Range.new(A.new, A.new)
#=> in `initialize': bad value for range (ArgumentError)

# Ruby 2.5
Range.new(A.new, A.new)
#=> in `<=>': hoge (RuntimeError)

Object#yield_self 追加

# tap は前からある (1.8 ?)
123.tap{|x| x*2}        #=> 123

# 2.5 から
123.yield_self{|x| x*2} #=> 246

Process.times の精度向上

# Ruby 2.4
% ruby -e 'p Process.times'
#<struct Process::Tms utime=0.06, stime=0.01, cutime=0.0, cstime=0.0>

# Ruby 2.5
ruby -e 'p Process.times'
#<struct Process::Tms utime=0.071065, stime=0.015792, cutime=0.0, cstime=0.0>

String#delete_prefix, delete_suffix, delete_prefix!, delete_suffix! 追加

"abcdefg".delete_prefix("abc") #=> "defg"
"abcdefg".delete_suffix("efg") #=> "abcd"

String#casecmp, casecmp? が例外を発生しない

# Ruby 2.4
"hoge".casecmp(123)  #=> TypeError

# Ruby 2.5
"hoge".casecmp(123)  #=> nil

String#grapheme_clusters, each_grapheme_cluster 追加

Unicodeの合成文字単位で処理

gaga = "がが"           # 1文字目は「か」と「゙」の合成文字
gaga.chars              #=> ["か", "゙", "が"]
gaga.grapheme_clusters  #=> ["が", "が"]

Thread.name=

スレッドに名前をつけることができる機能が Ruby 2.3からあったんですが、Windows 10 でも有効になったらしいです。Windows 使ってないので未確認です。

Thread.new do
  Thread.current.name = "hogehoge"
end
% ps -L -o pid,lwp,comm  -p 14655
  PID   LWP COMMAND
14655 14655 ruby
14655 14656 ruby-timer-thr
14655 14657 hogehoge

Thread#fetch 追加

key が無い場合に Hash#[key] は nil を返して、Hash#fetch(key) は例外を発生させるんですが、それと同じような振る舞いをする Thread#fetch が追加されました。

Thread.current[:hoge]            #=> nil
Thread.current.fetch(:hoge)      #=> KeyError "key not found: hoge"
Thread.current.fetch(:hoge, 123) #=> 123

KeyError#receiver, key 追加

例外が発生したオブジェクトとキーが例外に保持されるようになりました。

begin
  hash = {a: 123}
  hash.fetch(:b)
rescue KeyError => e
  e.receiver #=> {a: 123}
  e.key      #=> :b
end

Time.at で秒以下の単位を指定

Time.at(1511056368, 123456).nsec            #=> 123456000
Time.at(1511056368, 123456.789).nsec        #=> 123456789
Time.at(1511056368, 123456789, :nsec).nsec  #=> 123456789

Random.raw_seed が Random.urandom に名前変更


標準添付ライブラリ

Bundler Gem 標準添付

ERB#result_with_hash 追加

コンテキストの変数じゃなくて、ハッシュで値を渡すこともできるようになりました。

require "erb"
name = "tmtms"
ERB.new("Hello <%=name%>").result #=> "Hello tmtms"
require "erb"
ERB.new("Hello <%=name%>").result_with_hash(name: "tmtms")

ERB: trim 指定時 CR LF を trim

LF単独だと削除されましたが、CR LFは削除されなかったのが削除されるようになりました。

# Ruby 2.4
require 'erb'
ERB.new("<%=123%>\r\n", nil, 1).result #=> "123\r\n"

# Ruby 2.5
require 'erb'
ERB.new("<%=123%>\r\n", nil, 1).result #=> "123"

Net::HTTP ステータス追加

    '102' => Net::HTTPProcessing,
    '208' => Net::HTTPAlreadyReported,
    '421' => Net::HTTPMisdirectedRequest,
    '451' => Net::HTTPUnavailableForLegalReasons,
    '506' => Net::HTTPVariantAlsoNegotiates,
    '508' => Net::HTTPLoopDetected,
    '510' => Net::HTTPNotExtended,

Net::HTTP::STATUS_CODES 追加

require 'net/http/status'
Net::HTTP::STATUS_CODES
#=> {100=>"Continue", 101=>"Switching Protocols",
#    102=>"Processing", 200=>"OK", 201=>"Created",
#    202=>"Accepted", 203=>"Non-Authoritative Information",
#    204=>"No Content", 205=>"Reset Content", 206=>"Partial Content",
#    ...}

Net::HTTP.new に no_proxy 引数追加

no_proxy = "example.com,example.net:8080"
HTTP.new(address, port, proxy_addr, proxy_port,
         proxy_user, proxy_pass, no_proxy)

Net::HTTP: http_proxy環境変数を使用

HTTPプロキシ情報としてhttp_proxy環境変数が使われるようになりました。

Net::HTTP: http_proxy環境変数の認証情報を使用

http://user:password@proxy:port/

ただしWindowsでは使われないようです。環境変数がプロセスごとに独立してないから? ただし Linux, FreeBSD, Darwin に限られます。 ps コマンド等で、他のユーザーに環境変数の値が参照される可能性があるためのようです。

RbConfig::SIZE_OF, RbConfig::LIMITS 追加

require "rbconfig/sizeof"
RbConfig::SIZEOF
#=> {"int"=>4, "short"=>2, "long"=>8, "long long"=>8,
#    "__int128"=>16, "off_t"=>8, "void*"=>8, "float"=>4,
#    "double"=>8, "time_t"=>8, "clock_t"=>8, "size_t"=>8,
#    ...
RbConfig::LIMITS
#=> {"FIXNUM_MAX"=>4611686018427387903,
#    "FIXNUM_MIN"=>-4611686018427387904, "CHAR_MAX"=>127,
#    "CHAR_MIN"=>-128, "SCHAR_MAX"=>127, "SCHAR_MIN"=>-128,
#    "UCHAR_MAX"=>255, "WCHAR_MAX"=>2147483647,
#    ...

Ripper#state, Ripper::EXPR_BEG 追加

Set#to_s, #=== 追加

s = Set.new([1, 2, 3])
# Ruby 2.4
s.to_s   #=> "#<Set:0x00560b825243b8>"
s === 2  #=> false
s === s  #=> true

# Ruby 2.5
s.to_s   #=> "#<Set: {1, 2, 3}>"
s === 2  #=> true    Set#include? と同じ
s === s  #=> false

to_sはいいけど、=== は互換的な問題おきるかもしれないですね。

BasicSocket#read_nonblock, #write_nonblock が O_NONBLOCK をセットしない

require 'io/nonblock'
s = TCPSocket.new("127.0.0.1", 80)
s.nonblock?        #=> false
s.read_nonblock(10)

# Ruby 2.4
s.nonblock?        #=> true

# Ruby 2.5
s.nonblock?        #=> false

Socket::Ifaddr#vhid 追加

vhid が何なのかわからなかったです。BSDで使われてる何か?

WEBrick: Server Name Indication (SNI) サポート

SSL/TLSの何からしいです。よくわかってません。

標準ライブラリから mathn 削除

もうあまり使われてないから?

標準ライブラリから ubygems 削除

ruby はコマンドラインオプション -r でライブラリを読み込めるんですが、r で始まる名前のライブラリの場合は先頭の r を省いた名前のライブラリを用意することで、オプションの -r と合わせて自然に読めるようになります。

ubygems は rubygems の別名で、-rubygems コマンドラインオプションで使用できるようにするためのものですが、rubygems は require しなくても標準で使用されるためもう不要なのでした。


その他

バックトレースの順番

標準エラー出力が端末の場合に表示が逆順になります。

# Ruby 2.4
% ruby /tmp/a.rb
/tmp/a.rb:8:in `c': unhandled exception
    from /tmp/a.rb:5:in `b'
    from /tmp/a.rb:2:in `a'
    from /tmp/a.rb:10:in `<main>'
# Ruby 2.5
% ruby /tmp/a.rb
Traceback (most recent call last):
    3: from /tmp/a.rb:10:in `<main>'
    2: from /tmp/a.rb:2:in `a'
    1: from /tmp/a.rb:5:in `b'
/tmp/a.rb:8:in `c': unhandled exception

標準エラー出力が端末でなければ従来通り。

% ruby /tmp/a.rb 2>&1 | cat
/tmp/a.rb:8:in `c': unhandled exception
    from /tmp/a.rb:5:in `b'
    from /tmp/a.rb:2:in `a'
    from /tmp/a.rb:10:in `<main>'

configure で拡張ライブラリを強制

通常は configure 時の環境に応じて使えるライブラリが自動判別されますが、--with-ext オプションで強制することができます。コンパイルできる環境でない場合は make でエラーになります。

% ./configure --with-ext=openssl,+
% make
...
*** Following extensions are not compiled:
openssl:
    Could not be configured. It will not be installed.
    /tmp/ruby-2.5.0-preview1/ext/openssl/extconf.rb:94: OpenSSL library could not be found. You might want to use --with-openssl-dir=<dir> option to specify the prefix where OpenSSL is installed.
    Check ext/openssl/mkmf.log for more details.
*** Fix the problems, then remove these directories and try again if you want.
exts.mk:1853: ターゲット 'note' のレシピで失敗しました
make[1]: *** [note] エラー 1
make[1]: ディレクトリ '/tmp/ruby-2.5.0-preview1' から出ます
uncommon.mk:236: ターゲット 'build-ext' のレシピで失敗しました
make: *** [build-ext] エラー 2

結構非互換が多いような気はしますが、いろいろ便利になってますね。期待!