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
70287689548120
freeze されているので、文字列を破壊的に変更するようなメソッド呼び出しは、当然エラーになります。
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/}
# => 1
Enumerable#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
を返します。