Ruby 2.5 の変更内容 その2 - 組み込みライブラリ

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

長くなったので3つにわけてます。


いろいろ便利になっていますが、個人的には Hash#slice, Kernel#pp が嬉しいです。



配列/ハッシュ/Enumerable

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]

Enumerable#any?, #all?, #none?, #one? が引数で判定

今まではブロックの評価結果が真かどうかで判定していたのですが、単純な値の比較の場合は引数で与えられるようになりました。

引数を与えた場合は 引数 === 要素 の結果が真かどうかで判定されます。

[0, 1, 2].any?{|n| n == 1}  #=> true
[0, 1, 2].any?(1)           #=> true

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 から

Hash#slice 追加

Hashオブジェクトの指定したキーから成る新たなHashオブジェクトを返します。

h = {a: 1, b: 2, c: 3}
h.slice(:a, :c) #=> {a: 1, c: 3}

数値

Integer.sqrt 追加

平方根を整数で返す。

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

Integer#allbits?, #anybits?, #nobits? 追加

すべてのビットが立っている? いずれかのビットが立っている? いずれのビットも立っていない? を調べるメソッドです。

11.allbits?(10) #=> true
11.allbits?(12) #=> false
11.anybits?(12) #=> true
11.anybits?(4)  #=> false
11.nobits?(4)   #=> true
11.nobits?(4)   #=> false

Integer#pow 追加

2.pow(5)    #=> 32  2**5 と同じ
2.pow(5, 7) #=> 4   2**5%7 と同じ

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)

文字列

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

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

String#grapheme_clusters, each_grapheme_cluster 追加

Unicodeの合成文字単位で処理

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

String#undump 追加

String#dump の逆を行うメソッドです。

"a\n\t".dump         #=> "\"a\\n\\u3042\\t\""
"a\n\t".dump.undump  #=> "a\nあ\t"

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

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

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

String#start_with? が正規表現でも指定可

"123abc".start_with?("123") #=> true
"123abc".start_with?(/\d/)  #=> true

IO/ファイル

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"]

Dir.chdir, .open, .new, .mkdir, .rmdir, .empty? が GVL を解放する

マルチスレッドでの実行性能があがるかもしれません。

File.open の :newline オプションが指定されたらtextモードになる

今まではtextモードを指定しない場合は :newline オプションを指定しても効果がありませんでしたが、:newline オプションを指定すると自動的にtextモードになるようになったようです。

File.write("hoge", "a\r\n")

# Ruby 2.4
File.open("hoge", "rt", newline: :universal).gets  #=> "a\n"
File.open("hoge", newline: :universal).gets  #=> "a\r\n"

# Ruby 2.5
File.open("hoge", "rt", newline: :universal).gets  #=> "a\n"
File.open("hoge", newline: :universal).gets  #=> "a\n"

この :newline オプションはドキュメントに載っていないようですが、newline: :universaluniversal_newline: true と同じみたいです。

File::TMPFILE オプションで開いたファイルに対する File#path が IOError になる

File::TMPFILE は open(2) の O_TPMFILE と同じです。このオプションは指定したディレクトリに名前無しファイルを作成するためのオプションなのでファイル名はありませんが、今までは #path でディレクトリ名が返ってきていました。2.5 では IOError になります。

# Ruby 2.4
File.open("/tmp", File::WRONLY|File::TMPFILE).path #=> "/tmp"

# Ruby 2.5
File.open("/tmp", File::WRONLY|File::TMPFILE).path #=> File is unnamed (TMPFILE?) (IOError)

File のメソッドのいくつかが GVL を解放する

File.rename, File.readable?, File.readable_real?, File.writable?, File.writable_real?, File.executable?, File.executable_real?, File.mkfifo, File.readlink, File.truncate, File#truncate, File.chmod, File.lchmod, File.chown, File.lchown, File.unlink, File.utime, File.stat, File.lstat, File.exist?, その他 stat系メソッド

マルチスレッドでの実行性能があがるかもしれません。

File::Stat#atime, #mtime, #ctime が Windows 8以降で小数点以下の秒数をサポート

Windows 使ってないのでわかりません。Linux では以前から取得できました。

File.stat("hoge").mtime.strftime("%F %T.%N") #=> "2017-12-26 01:37:26.252224601"

File::Stat#ino, File.identical? が Windows 8.1 以降の ReFS 128bit ino をサポート

Windows 使ってないので何のことだかわかりません…

File.lutime 追加

リンク先ではなくリンクファイルそのもののタイムスタンプを更新するメソッドです。lutimes(3) 関数を使ってるようです。

IO.copy_stream が copy_file_range(2) を使って高速化

最近のLinuxにはcopy_file_rangeなんてシステムコールがあるんですな。カーネルレベルでファイルのコピーをして、ユーザー空間でデータを転送しないで済むので、オーバーヘッドがなくて速いらしいです。

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

IO#write が複数文字列を受けつける

引数に複数の文字列を指定した場合は writev(2) を使うので、事前に文字列を結合するよりも効率が良さそうです。

$stdout.write('abc', 'def', 'ghi')

例外系

Exception#full_message

例外を rescue しなければ標準エラー出力にエラー内容が出力されますが、それと同じ文字列を取り出すことが出来ます。

class A
  def initialize
    raise 'hoge'
  end
end

A.new
% ruby a.rb
Traceback (most recent call last):
        2: from a.rb:7:in `<main>'
        1: from a.rb:7:in `new'
a.rb:3:in `initialize': hoge (RuntimeError)
class A
  def initialize
    raise 'hoge'
  end
end

begin
  A.new
rescue => e
  p e.full_message
end
% ruby a.rb
"\e[1mTraceback \e[m(most recent call last):\n\t2: from a.rb:8:in `<main>'\n\t1: from a.rb:8:in `new'\na.rb:3:in `initialize': \e[1mhoge (\e[4;1mRuntimeError\e[m\e[1m)\n\e[m"

FrozenError 例外追加

frozen オブジェクトを更新するような操作で発生します。今までは RuntimeError が発生してました。 FrozenError は RuntimeError のサブクラスなので、通常はプログラムを変更する必要はないはずです。

# Ruby 2.4
"abc".freeze.upcase! #=> can't modify frozen String (RuntimeError)

# Ruby 2.5
"abc".freeze.upcase! #=> can't modify frozen String (FrozenError)

IOError のメッセージ "closed stream" が "stream closed in another thread" に変更

# Ruby 2.4
$stdin.close; $stdin.gets             #=> closed stream (IOError)
Thread.new{$stdin.close}; $stdin.gets #=> stream closed (IOError)

# Ruby 2.5
$stdin.close; $stdin.gets             #=> closed stream (IOError)
Thread.new{$stdin.close}; $stdin.gets #=> stream closed in another thread (IOError)

KeyError#receiver, key 追加

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

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

その他

Kernel#pp 追加

require 'pp' しなくても pp メソッドが使えるようになりました。便利。

Kernel#warn に uplevel オプション追加

uplevel を指定すると warn が発生したファイル名と行番号を出力します。

def a
  warn('a', uplevel: 1) # 1つ上の階層のファイル名と行番号を表示
end
def b
  a
end
def c
  b
end
c      #=> a.rb:5: warning: a

Method#=== 追加

Method#call と同じです。Proc#=== が Proc#call であるのに合わせたようです。

Module#attr, attr_accessor, attr_reader, attr_writer, define_method, alias_method, undef_method, remove_method が public

今まで Klass.class_eval{define_method(:hoge){}} みたいにしないといけなかったのが、Klass.define_method(:hoge){} と書けるようになりました。

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>

Process.last_status 追加

$? と同じ

RubyVM::InstructionSequence#each_child, trace_point 追加

よく知らないのでパス…

Struct.new に :keyword_init オプション追加

クラス生成時に :keyword_init を指定することでオブジェクト生成時にキーワード引数で値を設定することができます。

Hoge = Struct.new(:a, :b)
Hoge.new(123, 456)         #=> #<struct Hoge a=123, b=456>
Hoge = Struct.new(:a, :b, keyword_init: true)
Hoge.new(a: 123, b: 456)   #=> #<struct Hoge a=123, b=456>

Thread.name= が Windows でも有効

スレッドに名前をつけることができる機能が 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

Thread.report_on_exception がデフォルトで true

2.4 ではスレッドが例外が終了してもデフォルトでは何も出力されませんでした。 Thread.report_on_exception=true とすることで標準エラー出力に出力されましたが、2.5 ではこれがデフォルトで true になってます。

thr = Thread.new{ raise "hoge" }  # この時点でエラーが出力される
thr.join  #=> 例外発生 RuntimeError: hoge

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 に名前変更