Ruby 2.3.0-preview1 がリリースされたので新機能を試してみた
Frozen String Literal Pragma#
新しいマジックコメントまたはコマンドライン引数を指定することで、ソースコード中の全ての文字列リテラルを freeze するというもの。
Ruby 3 では全ての文字列リテラルが immutable (frozen) になるそうなので、2.3.0 では先行してそれを有効にできるようなったようです。
# string-literal-frozen.rb
a = "foo"
b = "foo"
puts a.object_id
puts b.object_id従来通りだと、別のオブジェクトとなります。
$ ruby string-literal-frozen.rb
70124014993580
70124014993560マジックコメントで
# frozen_string_literal: trueを指定すると、同じ文字列のリテラルは同じオブジェクトになります。
$ ruby string-literal-frozen.rb
70095964209440
70095964209440マジックコメントをつけずに、コマンドライン引き数で --enable=frozen-string-literal を指定することもできます。
$ ruby --enable=frozen-string-literal string-literal-frozen.rb
70287689548120
70287689548120freeze されているので、文字列を破壊的に変更するようなメソッド呼び出しは、当然エラーになります。
foo = "foo"
foo.upcase!
# can't modify frozen String (RuntimeError)なので、必要に応じて、.dup を呼び出して複製するか、String.new でインスタンスを生成する必要があります。
foo = "foo".dup
foo.upcase!
bar = String.new
bar << 'Ruby'Safe navigation operator#
メソッドをコールするときに &. というオペレーターを使う、新しいシンタックスが追加されています。
Rebuild.fm #118 で、「ぼっちオペレーター」とか言われてました。なるほど。
object&.foo のようにメソッド呼び出しをした際に、object が nil でなければメソッドがコールされますが、object が nil の場合は nil になります。
これまでだと、
buz = nil
if foo != nil && foo.bar != nil
buz = foo.bar.buz
endみたいに書かないといけなかったのが、
buz = foo&.bar&.buzだけでよくなります。便利ですね。
Array#dig, Hash#dig#
Array と Hash に、dig というメソッドが追加されました。
Array や Hash がネストされている場合に、深い階層にある要素を取り出すのに使います。
a = [[[1, 2, 3]]]
num = a.dig(0, 0, 1)
# => 2
num = a.dig(0, 1, 1)
# => nil
h = {foo: {bar: {buz: 'qux'}}}
str = h.dig(:foo, :bar, :buz)
# => "qux"
str = h.dig(:hoge, :bar, :buz)
# => nilぼっちオペレーターもそうでしたが、このメソッドを使うと、途中の階層の要素が存在するかどうかのチェックをしなくても良くなります。
Array#bsearch_index#
配列の要素を二分探索するメソッドとして、Array#bsearch がありますが、このメソッドは戻り値として、見つかった要素を返します(見つからなかった場合は nil を返します)。
Array#bsearch_index は、要素を返すのではなくインデックスを返します。見つからなかった場合は nil を返します。
a = ['foo', 'bar', 'buz']
a.bsearch {|s| s =~ /b/}
# => "bar"
a.bsearch_index {|s| s =~ /b/}
# => 1Enumerable#grep_v#
もともと存在する Enumerable#grep は、パラメーターと要素を === で比較し、マッチした要素の配列を返しますが、新しく追加された Enumerable#grep_v はマッチしなかった要素の配列を返します。つまり、grep の -v オプションです。Unix っぽいですね。
a = ['foo', 'bar', 'buz', 'qux']
a.grep(/b/)
# => ["bar", "buz"]
a.grep_v(/b/)
# => ["foo", "qux"]Enumerable#chunk_while#
Enumerable#slice_when は、隣り合う要素が前方から順に指定されたブロックに渡され、ブロック内で評価した結果が偽になるところでチャンクを切ります。メソッドは、チャンク分けされた要素を持つ Enumerator が戻り値として返します。
Enumerable#chunk_while は、既存の Enumerable#slice_when の逆バージョンです。
a = [1, 2, 4, 9, 10, 11, 12, 15, 16, 19, 20, 21]
a.slice_when {|i, j| i + 1 != j}.to_a
# => [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]
a.chunk_while {|i, j| i + 1 == j}.to_a
# => [[1, 2], [4], [9, 10, 11, 12], [15, 16], [19, 20, 21]]Hash#fetch_values#
Hash#fetch_values は、パラメーターで複数のキーを指定して、各キーの値を配列で返します。
キーが存在しない場合は、ブロックで評価された値を使用します。ブロックを指定しなかった場合は KeyError になります。
Hash#values_at と Hash#fetch を合体させたみたいな感じです。
h = {a: 1, b: 2, c: 3}
h.fetch_values(:a, :c)
# => [1, 3]
h.fetch_values(:a, :d)
# => KeyError: key not found: :d
h.fetch_values(:a, :d) {|key| 0}
# => [1, 0]Hash#⇐, Hash#<, Hash#>=, Hash#>#
2つのハッシュを比較する演算子が追加されました。
なかなか言葉で説明しづらい……。
{a: 1, b: 2} >= {a: 1}
# => true
{a: 1, b: 2} >= {a: 2}
# => false
{a: 1, b: 2} >= {a: 1, b:1}
# => false
{a: 1, b: 2} >= {a: 1, b:2}
# => true
{a: 1, b: 2} > {a: 1}
# => true
{a: 1, b: 2} > {a: 2}
# => false
{a: 1, b: 2} > {a: 1, b:2}
# => falseこんな感じです。
Hash#to_proc#
Hash#to_proc は、ハッシュのキーをパラメーターとして受け取り、その値を返す Proc になります。存在しないキーの場合は nil が返ります。
これにより、ブロックを受け取るメソッドに、Hash を渡すことができるようになります。
h = {a: 1, b: 2, c: 3}
[:a, :b, :c].map(&h)
# => [1, 2, 3]Numeric#positive?, Numeric#negative?#
Numeric#positive? は数値が正の値なら true、それ以外なら false を返します。
Numeric#negative? は数値が負の値なら true、それ以外なら false を返します。