Ruby 2.3 プレビュー(言語・組み込みライブラリ編)

Misoca開発チームのeitoballです。RubyKaigi2015 の開催を指折り数えて待っている今日この頃です。株式会社Misoca は、「Additional Sponsor」として、RubyKaigi2015 に協賛しています。

今日は、今年のクリスマスにリリースが予定されているRuby 2.3 の初めてのプレビューリリースが、11月11日に リリース されたので、NEWS を読みながら、新しく追加される予定の機能などを試していきたいと思います。


言語の変更

frozen string literal pragma(文字列リテラル凍結のプラグマ)の追加

文字列リテラル を記述すると、#freezeを呼び出すと同じように凍結されるプラグマが追加されます。

有効にするには、次のようにファイルの先頭にプラグマを書きます。

# frozen-string-literal: true

文字列リテラルを変更しようとすると例外(RuntimeError)が発生します。

$ cat a.rb
# frozen-string-literal: true
'a'.tr!('a', 'b')
$ ruby a.rb
a.rb:1:in `tr!': can't modify frozen String (RuntimeError)
        from a.rb:1:in `<main>'

インタプリタを実行する時、--enable=frozen-string-literal と書くことでも有効にすることができます。(無効にするには、--disable=frozen-string-literal

$ ruby --enable=frozen-string-literal -e '"a".tr!("b", "c")'
-e:1:in `tr!': can't modify frozen String (RuntimeError)
        from -e:1:in `<main>'

safe navigation operator (&.)の追加

ActiveSupportObject#try のように呼び出し元のオブジェクトが nil の場合でも、NoMethodError が発生することなく、メソッドを呼び出すことができる演算子です。

a = 'a'
a&.upcase # => "A"
a = nil
a&.upcase # => nil

Swift とか他の言語では、同じような演算子で、?. がありますが、Ruby では、メソッド名の最後に ? を使うことができるため、 &.になりました

ActiveSupport で、Object#try! というこれと似たような拡張があります。違いは、次の3つの場合です。

&. の後にはメソッド名が必要

obj.try! {} # ok
obj&. {} # syntax error, unexpected {, expecting '('

引数は実際に呼び出しが行われる場合に評価される

obj.try!(:foo, bar())  # bar() は、obj が、nil でも評価される
obj&.foo(bar())        # bar() は、obj が、nil ではなく、foo を呼び出す際に評価される

属性値への代入が可能

obj.try!(:attr) += 1 # SyntaxError: unexpected tOP_ASGN, expecting end-of-input
obj&.attr += 1 # ok

did_you_mean gem の追加

did_you_mean gem が、bundled gems に追加されます。この gem は、NameErrorNoMethodError が発生する場合において、本来、使おうとしていたと考えられる変数名やメソッド名を提案してくれます。

irb> obj = Object.new
=> #<Object:0x007ffa83827a00>
irb> oba.to_s
NameError: undefined local variable or method `oba' for main:Object
Did you mean?  obj
        from (irb):2
        from .../bin/irb:11:in `<main>'
irb> Class.to_t
NoMethodError: undefined method `to_t' for Class:Class
Did you mean?  to_s
        from (irb):3
        from .../bin/irb:11:in `<main>'

組み込みライブラリの変更

ARGF

ARGF.read_nonblock での、exception: false のサポート

IO#read_nonblock のように読み込み時に Errno::EAGAINErrno::EWOULDBLOCK が発生する代わりに :wait_readable を返すかどうかを指定します。また、true を指定した場合は既に EOF に達していれば EOFError の代わりに nil を返します。[Feature #11358]( 参考: instance method IO#read_nonblock

Array

Array#bsearch_index の追加

ary#bsearch_index { |x| bool } -> int or nil

二分木探索(バイナリーサーチ)で、与えた条件に合致する要素の位置を返します。合致する要素がない場合、 nil を返します。[Feature #10730]

[1, 2, 3, 4, 5].bsearch_index { |x| x > 2 } # => 2
[1, 2, 3, 4, 5].bsearch_index { |x| x > 6 } # => nil

Array#dig の追加

ary#dig(idx, ...) -> object

ネストした配列から要素を取得します。a[1][2] のように #[] を使って、要素を取得する場合、a[1]nil の場合、 NoMethodError になってしまいますが、a.dig(1, 2) では、例外が発生しないで、 nil が返ります。[Feature #11643]

[[0, 1], [2, 3], [4, 5]].dig(1, 1) # => 2
[[0, 1], [2, 3], [4, 5]].dig(3, 1) # => nil

Enumerable

Enumerable#grep_v の追加

enum.grep_v(pattern) -> array enum.grep_v(pattern) { |obj| block } -> array

Enumerable#grep の反対で、引数のパターンに合致しない要素を配列で返します。[Feature #11049]

['aa', 'bb', 'cc', 'dd', 'ee'].grep_v(/[bc]/)  # => ["aa",  "dd", "ee"]
['aa', 'bb', 'cc', 'dd', 'ee'].grep_v(/[bc]/)  # => ["bb",  "cc"]
['aa', 'bb', 'cc', 'dd', 'ee'].grep_v(/[bc]/)  { |item| item.upcase } # => ["BB",  "CC"]
['aa', 'bb', 'cc', 'dd', 'ee'].grep_v(/./) # => []

grep というコマンドラインプログラムで、-v (inVerse)というオプションは、パターンに合致しない行を出力しますが、これと同じ挙動です。

Enumerable#chunk_while の追加

enum.chunk_while { |elt_before, elt_after| bool } -> an_enumerator

条件に合致する要素をグループに分けて配列にします。[Feature #10769]

例えば、昇順にソートされた整数の配列から、連続する整数で分ける場合は、

irb> [1, 2, 4, 9, 10, 11, 12, 15, 16, 19, 20, 21].chunk_while{|i, j| i + 1 == j}.each { |c| p c }
[1, 2]
[4]
[9, 10, 11, 12]
[15, 16]
[19, 20, 21]
=> nil

File

File.mkfifo の追加

File.mkfifo(file_name, mode) -> 0

名前付きパイプ ファイルを作成します。[Feature #11536]

file_name で、作成するファイル名、mode で、ファイルのパーミッションを指定します。

irb で次のように名前付きパイプファイルに書き込むと

irb> File.mkfilo('fifo_test', 0600)
=> 0
irb> while true;  File.write('fifo_test', Time.now.to_s); sleep 10 end

別の irb にて、読み込むことができます。

irb> while true; p File.read('fifo_test'); sleep 10; end
"2015-11-11 11:11:00 +0900"
"2015-11-11 11:11:10 +0900"
"2015-11-11 11:11:20 +0900"
...

File::TMPFILE の追加

Linux 3.11 以上などで、O_TMPFILE が定義されている場合、File.new などで、このフラグを使うことで、指定したディレクトリに一時ファイルを作成することができます。作成されたファイルは、#close を呼び出した時点で削除されます。

f = File.open('/tmp', File::TMPFILE | File::CREAT | File::RDWR)
f.puts('Hello, world!')
f.close # => ここで削除される

Hash

Hash#fetch_values の追加

hsh#fetch(key, ...) -> array hsh#fetch(key, ...) { |key| block } -> array

ハッシュから指定したキーに関連づけられた値の配列を取得します。#values_at と異なり、該当するキーが登録されていない場合、例外KeyError が発生します。ブロックが与えられていればそのブロックを評価した値を返します。[Feature #10017]

{a: 1, b: 2, c: 3}.fetch_values(:a, :c) # => [1, 3]
{a: 1, b: 2, c: 3}.fetch_values(:d) # => KeyError: key not found: :d
{a: 1, b: 2, c: 3}.fetch_values(:d) { |k| nil } # => nil

Hash#dig の追加

Hash#dig(key, ...) -> object

Array#digと同じようにネストしたハッシュから要素を取得します。a[1][2] のように #[] を使って、要素を取得する場合、a[1]nil の場合、 NoMethodError になってしまいますが、a.dig(1, 2) では、例外が発生しないで、 nil が返ります。[Feature #11643]

配列とハッシュを混在させてアクセスすることも可能です。

{a: {b: 2, c: 3}, d: 4}.dig(:a, :b) # => 2
{a: {b: 2, c: 3}, d: 4}.dig(:e, :b) # => nil
[1, {b: 2, c: 3}, 4, 5].dig(1, :c) # => 3

Hash#<= Hash#< Hash#>= Hash#> の追加

[Feature #10984]

hash <= other -> true or false

自身(hash)が other と同じキーと関連づけられた値を含んでいるならば true を返します。そうでない場合には false を返します。

{a: 1} <= {a: 1, b: 2} #=> true
{a: 1, b: 2} <= {a: 1, b: 2} #=> true
{c: 3} <= {a: 1, b: 2} #=> false
{a: 1, c: 3} <= {a: 1, b: 2} #=> false
{a: 1, b: 2, c: 3} <= {a: 1, b: 2} #=> false

hash < other -> true or false

#<= とほぼ同じですが、other と全く同じキーと関連づけられた値を含んでいる場合はfalseを返します。

{a: 1} < {a: 1, b: 2} #=> true
{a: 1, b: 2} < {a: 1, b: 2} #=> false

hash >= other -> true or false

自身(hash)に other と同じキーと関連づけられた値を含んでいる場合には true を返します。そうでない場合には false を返します。

{a: 1, b: 2} >= {a: 1} #=> true
{a: 1, b: 2} >= {a: 1, b: 2} #=> true
{a: 1, b: 2} >= {c: 3} #=> false
{a: 1, b: 2} >= {a: 1, c: 3} #=> false
{a: 1, b: 2} >= {a: 1, b: 2, c: 3} #=> false

hash > other -> true or false

#>= とほぼ同じですが、other と全く同じキーと関連づけられた値を含んでいる場合はfalseを返します。

{a: 1, b: 2} > {a: 1} #=> true
{a: 1, b: 2} > {a: 1, b: 2} #=> false

Hash#to_proc の追加

hsh#to_proc ->an_proc

self に対応する Proc オブジェクトを返します。Proc#callの第一引数で指定されるキーに関連づけられた値を selfから取得します。[Feature #11653]

hsh = {a: 1, b: 2, c: 3}
prc = hsh.to_proc
prc.call(:a) # => 1
prc.call(:d) # => nil
%i(a b).map(&hsh) # => [1, 2]
%i(c d).map(&hsh) # => [3, nil]

IO

File::SHARE_DELETE の追加

Windows において、誰かが開いているファイルのファイル名を変更したりする場合にFile.newでの 第2引数の mode にこのフラグを指定します。Windows APICreateFileFILE_SHARE_DELETE フラグをつけると同等のようです。 [Feature #11218]

f = File.new('newfile', File::CREAT | File::RDWR | File::SHARE_DELETE) # => <File:newfile>

flags オプションの追加

IO.newIO.open の第3引数に指定できるオプション引数に File::EXCLFile::SHARE_DELETE など指定できる flags オプションが追加されました。 [Feature #12253]

IO.open(filename, 'w',  flags: File::EXCL)
IO.open(filename, 'w',  flags: File::SHARE_DELETE)

Kernel

Kernel#loop が返す値の変更

Kernel#loop のブロック内で、StopIteration が発生する場合、その例外の値(StopIteration#result)を返すようになりました。[Feature #11498]

enum = Enumerator.new do |yielder|
  yielder << 1
  yielder << 2
  :ok
end

loop do
  enum.next
end # => :ok

Module

Module#deprecate_constant の追加

定数(constant)が参照される場合、非推奨(deprecate)であることを警告されるようにします。[Feature #11398]

require 'timeout'
TimeoutError # warning: constant ::TimeoutError is deprecated

Foo = 1
Bar = Foo
class Object
  deprecate_constant :Bar
end
Bar # warning: constant ::Bar is deprecated

NameError

NameError#receiver の追加

NameErrorが発生する場合、原因となる定数や変数の参照元(レシーバー)オブジェクトを返します。[Feature #10881]

obj = Object.new
begin
  obj.no_such_name
rescue NameError => e
  e.receiver # => #<Object:0x00000000>
  e.name # => :no_such_name
end

Numeric

Numeric#positive? Numeric#negative? の追加

[Feature #11151]

positive? -> bool

自身が、正の数の場合、trueを返します。そうでない場合、 false を返します。

1.positive? # => true
2.0.positive? # => true
(1/3r).positive? # => true
-4.positive? # => false

negative? -> bool

自身が、負の数の場合、trueを返します。そうでない場合、 false を返します。

-1.negative? # => true
-2.0.negative? # => true
(-1/3r).negative? # => true
4.negative? # => false

ActiveSupport でも導入される予定です。 Numeric#positive? , Numeric#negative?

Proc

Proc#call の最適化

Proc#call が最適化され、#call メソッド自身は、メソッドフレームに追加しないで、ブロックを直接呼び出すようになりました。Proc#[]Proc#===、そして、Proc#yield も同様に最適化されています。Kernel.set_trace_funcTracePoint でもトレースされないようになります。 [Feature #11569]

$ ruby -ve 'Proc.new { puts caller(0..2) }.call'
ruby 2.3.0preview1 (2015-11-11 trunk 52539) [...]
-e:1:in `block in <main>'
-e:1:in `<main>'

現在(2.2.3)では、以下のようになり、2.3.0 では、Proc#call の呼び出しが減っていることがわかります。

$ ruby -ve 'Proc.new { puts caller(0..2) }.call'
ruby 2.2.3p173 (2015-08-18 revision 51636) [...]
-e:1:in `block in <main>'
-e:1:in `call'
-e:1:in `<mail>'

Thread

Thread#name Tread#name= の追加

スレッド(thread)の名前を取得したり、付けたりします。#inspect を呼び出す場合に付けた名前も表示されます。 [Feature #11251]

Thread#name -> string Thread#name=(name) -> string

thr1 = Thread.new { puts 'with name' }
thr1.name = 'with_name'
thr2 = Thread.new { puts 'without name' }

thr1.name # => "with_name"
thr2.name # => nil

p thr1 # => #<Thread:0x007f993c0878e8@_name_@a.rb:1 dead>
p thr2 # => #<Thread:0x007f993c0878e8@a.rb:2 dead>

この preview1 の段階では、バージョン2.3で、追加や変更になる言語機能や組み込みライブラリは、少なくともバージョン2.2で動いているコードには、ほとんど全く影響を与えないという印象を受けます。

反響があれば、標準ライブラリの変更についての記事も書きたいと思います。


修正履歴