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 (&.)の追加
ActiveSupport の Object#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 は、NameError や NoMethodError が発生する場合において、本来、使おうとしていたと考えられる変数名やメソッド名を提案してくれます。
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::EAGAIN 、 Errno::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#> の追加
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 API の CreateFile に FILE_SHARE_DELETE フラグをつけると同等のようです。 [Feature #11218]
f = File.new('newfile', File::CREAT | File::RDWR | File::SHARE_DELETE) # => <File:newfile>
flags オプションの追加
IO.new や IO.open の第3引数に指定できるオプション引数に File::EXCL や File::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? の追加
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_func や TracePoint でもトレースされないようになります。 [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で動いているコードには、ほとんど全く影響を与えないという印象を受けます。
反響があれば、標準ライブラリの変更についての記事も書きたいと思います。
修正履歴
- [2015-11-20 22:00] "safe navigation operator" の説明で、「ActiveSupport で、
Object#try...」を「ActiveSupport で、Object#try!...」に修正しました。また、その後の1番目と3番目のコード例もtry!を使うようにしました。( https://twitter.com/n0kada/status/667649792973996032 )