サンプルコードでわかる!Ruby 2.4の新機能と変更点

More than 1 year has passed since last update.

はじめに

2016年9月にRuby 2.4のpreview2が、11月にpreview3が、12月11日にrc1がリリースされました(参考1参考2参考3)。
僕は早速インストールして新機能を試してみましたが、みなさんはいかがでしょうか?

というわけで、この記事では僕が実際に動かして確認したRuby 2.4の変更点や新機能をサンプルコード付きでまとめます。

この記事は大きく分けて次のセクションに分かれています。

  • 文法上の変更点
  • 後方互換性が失われる変更点
  • パフォーマンス改善
  • オブジェクト全般の新機能
  • 主に数値に関連する新機能
  • 主に文字列・正規表現に関連する新機能
  • 主に配列・ハッシュに関連する新機能
  • ディレクトリやファイルに関連する新機能
  • その他の標準ライブラリに関連する新機能
  • スレッドに関連する新機能

なお、Ruby 2.4は2016年12月25日にリリースされる予定です。
本記事はrc1時点のものなので、正式リリースまでに内容が変わる可能性もあります。

2016.12.25追記:正式リリース版でも動作検証しました

予定通り、2016年12月25日にRuby 2.4.0が正式リリースされました。

Ruby 2.4.0 リリース

下記の内容のほとんどはMinitestで動作検証しています。
Ruby 2.4.0でも全部のテストがパスしたので、正式リリース版でも本記事の内容は有効だと言えると思います。

本記事の情報源

本記事は以下のページに記載されている変更点をまとめたものです。

また、サンプルコードやパフォーマンスの計測値については下記サイトを一部参考にさせてもらいました。

New Features in Ruby 2.4 - BlockScore Blog

主要な変更点は本記事の内容でカバーできているはずですが、マイナーな変更点まではカバーできていないかもしれません。

また、説明している内容に間違いがあれば、コメントや編集リクエスト等で優しく指摘してやってください。

本記事で使ったサンプルコードのリポジトリ

本記事で使ったサンプルコード(テストコード)は以下のリポジトリにあります。

https://github.com/JunichiIto/ruby-2-4

Minitestの簡単な説明

本文中ではMinitestを使ったテストコードが登場します。
Minitestのコードの意味がよくわからない、という人は最低限、以下の検証メソッドの意味を理解していれば、なんとなく内容がつかめるかと思います。

# aはbに等しい
assert_equal b, a

# aはnil
assert_nil a

# aは真(true、またはnilでもfalseでもない値)
assert a

# aは偽(nilまたはfalse)
refute a

# a.foo() を実行するとFooErrorが発生する
assert_raises(FooError) { a.foo() }

# 呼び出された時点でテストを失敗させる
flunk

それでは以下が本編です。先にPreview 2での変更点を載せ、それからPreview 3とrc1の変更点を載せます。

文法上の変更点

条件式での多重代入が可能になった

Ruby 2.4では条件式で多重代入が使えるようになりました。
以下は引数によって配列またはnilを返すメソッドを条件式内で使うコード例です。

def some_method_returning_array_or_nil(flag)
  flag ? [1, 2] : nil
end

def test_multiple_assignment_in_conditional_expression
  # 配列が返ると真と判定される
  if (a, b = some_method_returning_array_or_nil(true))
    assert true
  else
    flunk 'should be true'
  end

  # nilが返ると偽と判定される
  if (a, b = some_method_returning_array_or_nil(false))
    flunk 'should be false'
  else
    assert true
  end
end

なお、Ruby 2.3以前では上記のコードはmultiple assignment in conditional (SyntaxError)というエラーが発生して実行できませんでした。

参考: Feature #10617: Change multiple assignment in conditional from parse error to warning

後方互換性が失われる変更点

FixnumクラスとBignumクラスがIntegerクラスに統合された

Ruby 2.3までは整数は基本的にFixnumクラスで、極端に大きいまたは小さい数になるとBignumクラスに自動的に変わっていましたが、Ruby 2.4では両者の親クラスであるIntegerクラスにまとめられます。

def test_unify_Fixnum_and_Bignum_into_Integer
  assert_equal Integer, 1.class
  assert_equal Integer, -1.class
  assert_equal Integer, 10000000000000000000000000000.class
  assert_equal Integer, -10000000000000000000000000000.class
end

また、FixnumBignumを定数として参照すると、Integerが返ってきます。

Fixnum
#=> Integer

Bignum
#=> Integer

よって以下のテストコードはすべてパスします。

def test_unify_Fixnum_and_Bignum_into_Integer
  a = 1

  assert a.kind_of?(Integer)
  assert a.kind_of?(Fixnum)
  assert a.kind_of?(Bignum)

  assert a.is_a?(Integer)
  assert a.is_a?(Fixnum)
  assert a.is_a?(Bignum)

  assert Integer === a
  assert Fixnum === a
  assert Bignum === a
end

Ruby 2.4では「Fixnum = Bignum = Integer」なので、FixnumやBignumを独自に拡張していた場合は、Integerクラスを拡張したことと同じになります。
また、FixnumとBignumで同名のメソッドを定義していた場合は、後勝ちになるようです。

class ::Fixnum
  def foo
    'foo'
  end
  def baz
    'baz'
  end
end

class ::Bignum
  def bar
    'bar'
  end
  def baz
    'baz!!'
  end
end

def test_extend_Integer
  a = 1
  assert_equal 'foo', a.foo
  assert_equal 'bar', a.bar
  assert_equal 'baz!!', a.baz
end

Preview 3からはBignumやFixnumを参照すると警告が出るようになっています。

irb(main):001:0> Fixnum
(irb):1: warning: constant ::Fixnum is deprecated
=> Integer
irb(main):002:0> Bignum
(irb):2: warning: constant ::Bignum is deprecated
=> Integer

参考: Feature #12005: Unify Fixnum and Bignum into Integer

Enumerableモジュールにsumが追加された

Ruby 2.4ではEnumerableモジュールにsumメソッドが追加されました。
これにより、配列の中身の合計値を求めたりできます。

[1, 2, 3, 4].sum
#=> 10

デフォルトの初期値は0ですが、引数で変更できます。

[].sum
#=> 0

[1, 2, 3, 4].sum(5)
#=> 15

小数の合計値は丸め誤差が発生するので注意してください。

[0.1, 0.1, 0.1].sum
#=> 0.30000000000000004

ただし、sumメソッドでは内部的にカハンの加算アルゴリズムが使われているため、+演算子で加算したときよりも高速で誤差が少なくなりやすいです。

# 要素が3の場合、誤差は同じ
[0.1, 0.1, 0.1].sum
#=> 0.30000000000000004
0.1 + 0.1 + 0.1
#=> 0.30000000000000004

# 要素が6の場合は+の方が誤差がない
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1].sum
#=> 0.6000000000000001
0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
#=> 0.6

# 要素が10の場合はsumの方が誤差がない
array = Array.new(10, 0.1)
array.sum
#=> 1.0
# 素直に+で書くとコードが長くなるのでinjectで代用
array.inject(:+)
#=> 0.9999999999999999

# 要素が99999の場合はsumの方が誤差が小さい
array = Array.new(99999, 0.1)
array.sum
#=> 9999.900000000001
array.inject(:+)
#=> 9999.900000018848

文字列の配列を連結する場合は初期値に何らかの文字列を渡します。

['foo', 'bar'].sum('')
#=> 'foobar'

['foo', 'bar'].sum('>>')
#=> '>>foobar'

配列の配列を連結することも可能です。

[[1, 2], [3, 1, 5]].sum([])
#=> [1, 2, 3, 1, 5]

Ruby単体で見たときは後方互換性の問題は生じませんが、ActiveSupport等のサードパーティgemでsumが実装されていた場合は完全に互換性があるとは限らないので注意が必要、とのことです。

参考: Feature #12217: Introducing Enumerable#sum for precision compensated summation and revert r54237

非ASCII文字でも小文字化、大文字化されるようになった

Ruby 2.4では非ASCII文字(たとえばウムラウトが含まれるような文字)でも小文字化、大文字化されるようになりました。

def test_non_ascii_upcase_downcase_swapcase_capitalize
  assert_equal 'TÜRKIYE', 'Türkiye'.upcase
  assert_equal 'türkiye', 'Türkiye'.downcase
  assert_equal 'tÜRKIYE', 'Türkiye'.swapcase
  assert_equal 'Ürkiye', 'ürkiye'.capitalize

  # 破壊的メソッドも同じ挙動になる
  a = 'Türkiye'
  a.upcase!
  assert_equal 'TÜRKIYE', a
end

この変更が望ましくない場合(Ruby 2.3と同じようにASCII文字だけを変換したい場合)は:asciiオプションを付けてください。

'Türkiye'.upcase(:ascii)
#=> "TüRKIYE"

参考: Feature #10085: Add non-ASCII case conversion to String#upcase/downcase/swapcase/capitalize

Symbol#matchがMatchDataを返すようになった(不具合修正)

Ruby 2.3まではSymbol#matchはマッチした位置を返す不具合がありました。

:"".match(//) 
# => 0

Ruby 2.4ではString#matchと同じようにMatchDataオブジェクトを返します。

m = :"".match(//) 
#=> #<MatchData "">
m[0]
#=> ""

参考: Bug #11991: `Symbol#match` returns the match position, unlike `String#match` and `Regexp#match`

DateTimeとTimeのto_timeでタイムゾーンを保持するようになった(不具合修正)

Ruby 2.4ではDateTimeクラスとTimeクラスのto_timeメソッドを呼びだした際、元のオブジェクトのタイムゾーンを保持するようになりました。

def test_date_time_to_time
  require 'date'
  cet_date_time = DateTime.strptime('2015-11-12 CET', '%Y-%m-%d %Z')
  assert_equal '2015-11-12 00:00:00 +0100', cet_date_time.to_time.to_s
end

def test_time_to_time
  cet_time = Time.new(2005, 2, 21, 10, 11, 12, '+01:00')
  assert_equal '2005-02-21 10:11:12 +0100', cet_time.to_time.to_s
end

Ruby 2.3以前ではto_timeメソッドを呼び出すと、実行環境のタイムゾーンに変わる不具合がありました。

# 実行環境がJSTだと、to_timeの結果もJSTになる(Ruby 2.3以前)
DateTime.strptime('2015-11-12 CET', '%Y-%m-%d %Z').to_time.to_s
#=> "2015-11-12 08:00:00 +0900"

Time.new(2005, 2, 21, 10, 11, 12, '+01:00').to_time.to_s
#=> "2005-02-21 18:11:12 +0900"

参考: Bug #12189: DateTime#to_time removes timezone information

参考: Bug #12271: `Time#to_time` removes timezone information

Ruby/Tkが標準ライブラリから外された

Ruby 2.4ではRuby/Tkが標準ライブラリから外されて外部gemになりました。

https://github.com/ruby/tk

Rubyインストール時には一緒にインストールされないため、必要であれば別途インストールする必要があります。

参考: Feature #8539: Unbundle ext/tk

パフォーマンス改善

Arrayクラス独自のmaxとminが実装され高速化した

Ruby 2.4ではArrayクラス独自のmaxメソッドとminメソッドが実装され高速化しました。
Ruby 2.3まではEnumerableモジュールのmaxメソッドとminメソッドが使われていました。

下記サイトの計測結果によると、1.61倍ほど高速化したとのことです。

New Features in Ruby 2.4 - BlockScore Blog

require 'benchmark/ips'

Benchmark.ips do |bench|
  NUMS = 1_000_000.times.map { rand }

  # By binding the Enumerable method to our array
  # we can bench the previous speed in Ruby 2.3
  ENUM_MIN  = Enumerable.instance_method(:min).bind(NUMS)

  # Bind the `#min` method to our test array also
  # so our benchmark code is as similar as possible
  ARRAY_MIN = Array.instance_method(:min).bind(NUMS)

  bench.report('Array#min') do
    ARRAY_MIN.call
  end

  bench.report('Enumerable#min') do
    ENUM_MIN.call
  end

  bench.compare!
end

# >> Warming up --------------------------------------
# >>            Array#min     3.000  i/100ms
# >>       Enumerable#min     2.000  i/100ms
# >> Calculating -------------------------------------
# >>            Array#min     35.147  (± 2.8%) i/s -    177.000  in   5.039133s
# >>       Enumerable#min     21.839  (± 4.6%) i/s -    110.000  in   5.040531s
# >> Comparison:
# >>            Array#min:       35.1 i/s
# >>       Enumerable#min:       21.8 i/s - 1.61x slower

参考: Feature #12172: Array#max and Array#min

オブジェクト全般の新機能

指定された範囲内の値を返すようにするComparable#clamp

Ruby 2.4ではclampメソッドを使って、指定した範囲の値を返すことができます。

「指定した範囲の値を返す」というのは厳密に言うと、下限値と上限値を引数で指定し、レシーバの値が、

  • その範囲内であればレシーバを返す
  • 下限を超えていれば下限値を返す
  • 上限を超えていれば上限値を返す

というものです。

def test_comparable_clamp
  # 下限が1、上限が3で2が渡された場合 => 範囲内なのでそのまま2を返す
  assert_equal 2, 2.clamp(1, 3)
  # 下限が1、上限が3で0が渡された場合 => 下限値の1を返す
  assert_equal 1, 0.clamp(1, 3)
  # 下限が-2、上限が-1で0が渡された場合 => 上限値の-1を返す
  assert_equal -1, 0.clamp(-2, -1)
  # 下限が'a'、上限が'b'で'c'が渡された場合 => 上限値の'b'を返す
  assert_equal 'b', 'c'.clamp('a', 'b')
end

clampメソッドに渡す引数は必ず下限値、上限値の順で渡す必要があります。逆で渡すとエラーになります。

2.clamp(3, 1)
#=> ArgumentError: min argument must be smaller than max argument

なお、clampというメソッド名は電子回路で使われる「クランプ回路」に由来するようです。

過電流や短絡電流の対策に使える電流クランプ回路 - EDN Japan

参考: Feature #10594: Comparable#clamp

freeze状態までcloneするかどうかをオプションで指定できるようになった

freezeしたオブジェクトをcloneした場合、Ruby 2.3まではcloneしたオブジェクトまでfreezeされていましたが、Ruby 2.4ではfreezeするかどうかをfreezeオプションで指定できます。

def test_clone_with_freeze_keyword_option
  a = 'A'.freeze
  assert a.frozen?
  # デフォルトではfreeze状態もcloneされる
  assert a.clone.frozen?
  # cloneはするがfreezeはしない
  refute a.clone(freeze: false).frozen?
end

参考: Feature #12300: Allow Object#clone to take freeze: false keyword argument to not freeze the clone

主に数値に関連する新機能

切り上げ、切り下げ、切り捨てで小数点の位置を指定できるようになった

Ruby 2.4ではceilfloortruncateの各メソッドに引数を渡して、切り上げ、切り下げ、切り捨てを行う小数点の位置を指定できるようになりました。

def test_ceil_floor_truncate
  # 切り上げ
  assert_equal 2, 1.11.ceil
  assert_equal -1, -1.11.ceil
  assert_equal 1.2, 1.11.ceil(1)
  assert_equal -1.1, -1.11.ceil(1)
  # 負の値も指定可能
  assert_equal 11120, 11111.ceil(-1)
  assert_equal -11110, -11111.ceil(-1)

  # 切り下げ
  assert_equal 1, 1.99.floor
  assert_equal -2, -1.99.floor
  assert_equal 1.9, 1.99.floor(1)
  assert_equal -2, -1.99.floor(1)
  # 負の値も指定可能
  assert_equal 11110, 11119.floor(-1)
  assert_equal -11120, -11119.floor(-1)

  # 切り捨て(0から自身までの数値で、自身にもっとも近い整数または小数を返す)
  assert_equal 1, 1.99.truncate
  assert_equal -1, -1.99.truncate
  assert_equal 1.9, 1.99.truncate(1)
  assert_equal -1.9, -1.99.truncate(1)
  # 負の値も指定可能
  assert_equal 190, 199.truncate(-1)
  assert_equal -190, -199.truncate(-1)
end

参考: Feature #12245: optional parameter ndigits to Integer#floor, Integer#ceil, Float#floor, Float#ceil

整数を1桁ずつ配列に入れて返すInteger#digits

Ruby 2.4では整数を1桁ずつ配列に入れて返すInteger#digitsメソッドが追加されました。値は1桁目、2桁目・・・の順番で格納されます。

123.digits
#=> [3, 2, 1]

基数を指定するとn進数の1桁目、2桁目・・・と値を格納することもできます。

0x7b.digits(16)
#=> [11, 7]

123.digits(16)
#=> [11, 7]

マイナスの値に対してdigitsを呼び出すとエラーになります。

-123.digits
#=> Math::DomainError: out of domain

参考: Feature #12447: Integer#digits for extracting digits of place-value notation in any base

主に文字列・正規表現に関連する新機能

正規表現に文字列がマッチしたかどうかだけを返すRegexp#match?、String#match?、Symbol#match?

Ruby 2.4では正規表現に文字列がマッチしたかどうかだけを返すRegexp#match?メソッドが追加されました。
このメソッドはmatchメソッドよりも高速で、なおかつグローバル変数の$~を変更しない、という特徴もあります。

def test_regexp_match?
  # match?メソッドはグローバル変数の$~を変更しない
  assert /\d+-\d+-\d+/.match?('2016-09-01')
  assert_nil $~

  # 以下のメソッドは$~を変更する
  assert /\d+-\d+-\d+/.match('2016-09-01')
  assert_equal '2016-09-01', $~[0]

  assert /\d+-\d+-\d+/ =~ '2016-09-02'
  assert_equal '2016-09-02', $~[0]

  assert /\d+-\d+-\d+/ === '2016-09-03'
  assert_equal '2016-09-03', $~[0]
end

以下の記事ではmatch?メソッドと既存のメソッドの速度比較が載っています。
たとえばmatchメソッドに比べるとmatch?メソッドは4.88倍速いそうです。

New Features in Ruby 2.4 - BlockScore Blog

なお、matchメソッドとは異なり、Stringクラスにはmatch?メソッドは定義されていません。
rc1にてStringクラス、Symbolクラスにもmatch?メソッドが実装されました。

'2016-09-01'.match?(/\d+-\d+-\d+/)
#=> true

:hello_world.match?(/\w+_\w+/)
#=> true

参考: Feature #8110: Regex methods not changing global variables

名前付きキャプチャをハッシュとして返すMatchData#named_captures

Ruby 2.4では正規表現にマッチした名前付きキャプチャをハッシュとして返すMatchData#named_capturesメソッドが追加されました。
ハッシュのキーはシンボルではなく文字列になります。

def test_match_data_named_captures
  m = /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/.match('2016-09-01')
  assert_equal(
      {'year' => '2016', 'month' => '09', 'day' => '01'},
      m.named_captures
  )
end

参考: Feature #11999: MatchData#to_h to get a Hash from named captures

MatchData#values_atで名前付きキャプチャの名前を指定できるようになった

Ruby 2.3まではMatchData#values_atメソッドで指定できるのは、キャプチャした文字列のインデックスだけでしたが、Ruby 2.4では名前付きキャプチャの名前を指定できるようになりました。
名前は文字列、またはシンボルで指定できます。

def test_match_data_values_at
  m = /(?<year>\d+)-(?<month>\d+)-(?<day>\d+)/.match('2016-09-01')
  assert_equal(['2016', '01'], m.values_at(1, 3))
  # Ruby 2.4からは文字列またはシンボルで名前を指定可能
  assert_equal(['2016', '01'], m.values_at('year', 'day'))
  assert_equal(['2016', '01'], m.values_at(:year, :day))
end

参考: Feature #9179: MatchData#values_at should support named capture

Stringのcapacityを指定できるようになった

Ruby 2.4ではString.newcapacityオプションを渡せるようになりました。
長い文字列を追加することが事前に分かっている場合は、このオプションを指定することでパフォーマンスを向上させることができます。

以下のサイトの計測結果によると、capacityを指定すると2.32倍速くなったそうです。

New Features in Ruby 2.4 - BlockScore Blog

require 'benchmark/ips'

Benchmark.ips do |bench|
  bench.report("Without capacity") do
    append_me = ' ' * 1_000
    template  = String.new

    100.times { template << append_me }
  end

  bench.report("With capacity") do
    append_me = ' ' * 1_000
    template  = String.new(capacity: 100_000)

    100.times { template << append_me }
  end

  bench.compare!
end

# >> Warming up --------------------------------------
# >>     Without capacity     1.690k i/100ms
# >>        With capacity     3.204k i/100ms
# >> Calculating -------------------------------------
# >>     Without capacity     16.031k (± 7.4%) i/s -    160.550k in  10.070740s
# >>        With capacity     37.225k (±18.0%) i/s -    362.052k in  10.005530s
# >>
# >> Comparison:
# >>        With capacity:    37225.1 i/s
# >>     Without capacity:    16031.3 i/s - 2.32x slower

ちなみに、capacityを指定してもそれ以上文字が追加できなくなるわけではありません。

def test_string_with_capacity
  a = String.new(capacity: 1)
  a << '12345'
  assert_equal '12345', a
end

参考: Feature #12024: Add String.buffer, for creating strings with large capacities

StringクラスとRegexpクラスのUnicodeバージョンが9.0にアップデート

Ruby 2.4ではStringクラスとRegexpクラスのUnicodeバージョンが8.0から9.0にアップデートされました。

これにより、たとえば正規表現で西夏文字(せいかもじ)を表すTangutプロパティ(Unicode 9.0で追加されたプロパティ)を使えたりするようになります。

def test_unicode_9_0
  assert "A\u{17000}B".match(/\p{Tangut}/)
end

ちなみに\u{17000}というのはこんな文字です。(大半のブラウザでは表示できないはずです)

u17000@5.50px.png
u17000 - GlyphWiki

参考: Feature #12513: Update Unicode data to Unicode Version 9.0

主に配列・ハッシュに関連する新機能

指定した条件で重複をなくすEnumerable#uniq

Ruby 2.4ではEnumerableモジュールにuniqメソッドが追加されたため、配列だけでなく、ハッシュでもuniqが使えるようになります。
重複をなくす条件はブロックで指定します。

def test_enumerable_uniq
  # 値に'Athens'が2回登場している
  olimpics = {
      1896 => 'Athens',
      1900 => 'Paris',
      1904 => 'Chikago',
      1906 => 'Athens',
      1908 => 'Rome'
  }
  # 同じ値なら重複していると見なす
  each_city_first_time = olimpics.uniq { |k, v| v }
  # 1896の'Athens'だけが残る
  assert_equal [
      [1896, "Athens"],
      [1900, "Paris"],
      [1904, "Chikago"],
      [1908, "Rome"]
  ], each_city_first_time
end

配列とは異なり、破壊的メソッドのuniq!は用意されません。

olimpics.uniq! { |k, v| v }
#=> undefined method `uniq!' for #<Hash:0x007fa9f505bb48>

Enumerator::Lazyでもuniqが使えます。

# 2乗して10で割った余りを重複の判定条件にする
(1..Float::INFINITY).lazy.uniq { |x| (x**2) % 10 }.first(6)
#=> [1, 2, 3, 4, 5, 10]

参考: Feature #11090: Enumerable#each_uniq and #each_uniq_by

ハッシュの値を特定のルールで変更するHash#transform_values

Ruby 2.4ではHashクラスにtransform_valuesメソッドが追加され、ハッシュの値を特定のルールで変更できるようになりました。
破壊的な変更を行うtransform_values!メソッドもあります。

def test_hash_transform_values
  x = {a: 1, b: 2, c: 3}
  # ハッシュの値を2乗する
  y = x.transform_values {|v| v ** 2 }
  assert_equal({a: 1, b: 4, c: 9}, y)
  # 変数xの中身は変わっていない
  assert_equal({a: 1, b: 2, c: 3}, x)

  # 破壊的メソッドを適用すると変数xの中身が変わる
  x.transform_values! {|v| v ** 2 }
  assert_equal({a: 1, b: 4, c: 9}, x)
end

ちなみにこのメソッドはRuby on Rails(ActiveSupport)の同名メソッドをRuby本体にインポートしたものです。

http://api.rubyonrails.org/classes/Hash.html#method-i-transform_values

参考: Feature #12512: Import Hash#transform_values and its destructive version from ActiveSupport

ディレクトリやファイルに関連する新機能

ディレクトリが空かどうかを判定するDir.empty?

Ruby 2.4ではDir.empty?というメソッドが追加されました。
その名の通り、ディレクトリが空であればtrueを返すメソッドです。

def test_dir_empty?
  Dir.mktmpdir do |dir|
    assert Dir.empty?(dir)
  end
end

参考: Feature #10121: Dir.empty?

ファイルが空かどうかを判定するFile.empty?

Ruby 2.4ではFile.zero?のエイリアスメソッドとして、File.empty?が追加されました。
その名の通り、ファイルが空であればtrueを返すメソッドです。

def test_file_empty?
  require 'tempfile'
  Tempfile.create("foo") do |f|
    assert File.zero?(f)
    # empty?はzero?のエイリアスメソッド
    assert File.empty?(f)
  end
end

参考: Feature #9969: Add File.empty? as alias to File.zero?

その他の標準ライブラリに関連する新機能

CSVデータでダブルクオートの不正なフォーマットを許容するliberal_parsingオプション

Ruby 2.4ではCSVデータをパースする際に、ダブルクオートを使った不正なフォーマットのCSVデータを許容するliberal_parsingオプションが追加されました。

ダブルクオートを使った不正なフォーマットのCSVデータというのは、たとえばこんな文字列のことです。

"Johnson, Dwayne",Dwayne "The Rock" Johnson

本来であれば以下のような文字列にするのが正です。
(値にダブルクオートが含まれる場合は、ダブルクオートで囲んだ上で、ダブルクオートを重ねる)

"Johnson, Dwayne","Dwayne ""The Rock"" Johnson"

以下は不正なCSVデータをliberal_parsingオプションを付けてパースするコード例です。

def test_csv_liberal_parsing_option
  require 'csv'

  # ダブルクオートを使った不正なフォーマットのCSVデータを用意する
  input = '"Johnson, Dwayne",Dwayne "The Rock" Johnson'

  # liberal_parsingオプションなしだとエラー
  assert_raises(CSV::MalformedCSVError) { CSV.parse_line(input) }

  # liberal_parsingオプションありだとパース可能
  assert_equal(
      ['Johnson, Dwayne', 'Dwayne "The Rock" Johnson'],
      CSV.parse_line(input, liberal_parsing: true)
  )
end

参考: Feature #11839: CSV: liberal_parsing option

Logger.newのキーワード引数追加

Ruby 2.4ではLogger.newのキーワード引数としてlevelprognameformatterdatetime_formatが追加されました。

def test_logger_options
  require 'logger'
  formatter = proc { |severity, timestamp, progname, msg| "#{severity}:#{msg}\n\n" }
  logger = Logger.new(
      STDERR,
      level: :info,
      progname: :progname,
      formatter: formatter,
      datetime_format: "%d%b%Y@%H:%M:%S"
  )
  assert_equal Logger::INFO, logger.level
  assert_equal :progname, logger.progname
  assert_equal formatter, logger.formatter
  assert_equal "%d%b%Y@%H:%M:%S", logger.datetime_format
end

ちなみに、Ruby 2.3以前ではインスタンス作成後にプロパティをセットする必要がありました。

logger = Logger.new($stdout)
logger.level = :info

参考: Feature #12224: logger: Allow specifying log level in constructor

ログのローテーションファイルの拡張子書式を指定できるようになった

Ruby 2.3以前では、ログのローテーションファイルの拡張子書式は"%Y%m%d"固定になっていましたが、Ruby 2.4ではshift_period_suffixオプションを使って書式を指定できるようになりました。

require 'logger'
# development.log.2016-09-01
# development.log.2016-09-01.1
# development.log.2016-09-01.2
# のような名前のログファイルを出力する
logger = Logger.new('development.log', shift_period_suffix: '%Y-%m-%d')

参考: Feature #10772: Add ability to change rotated log file extention

optparseで起動時引数をハッシュに格納するintoオプション

optparseで起動時引数をパースする際、intoオプションを使ってパースした結果をハッシュに格納できるようになりました。

def test_optparse_into_option
  require 'optparse'
  require 'optparse/date'
  require 'optparse/uri'

  cli =
      OptionParser.new do |options|
        options.define '--from=DATE',    Date
        options.define '--url=ENDPOINT', URI
        options.define '--names=LIST',   Array
      end

  config = {}

  args = %w[
    --from  2016-02-03
    --url   https://blog.blockscore.com/
    --names John,Daniel,Delmer
  ]

  # パースした結果を変数configに格納する
  cli.parse(args, into: config)

  assert_equal(
      {
          from: Date.parse('2016-02-03'),
          url: URI('https://blog.blockscore.com/'),
          names: %w(John Daniel Delmer)
      },
      config
  )
end

上記のコードは以下の記事を参考にしました。

New Features in Ruby 2.4 - BlockScore Blog

参考: Feature #11191: Add #to_h method to OptionParser

OpenSSLが外部gemとして切り出された

Ruby 2.4ではOpenSSLが標準ライブラリではなく、外部のgemになりました。

https://github.com/ruby/openssl

ただし、Rubyインストール時にOpenSSL gemも一緒にインストールされるため、通常の使用では特に変化はありません。

また、機能面ではOpenSSL 1.1.0をサポートするようになったとのことです。

参考: Feature #9612: Gemify OpenSSL

スレッドに関連する新機能

別スレッドの例外を報告するかどうかを決めるThread.report_on_exceptionフラグ

Ruby 2.4では別スレッドで発生した例外を報告するかどうかを決めるThread.report_on_exceptionフラグが追加されました。
このフラグをtrueにすると、別スレッドで例外が発生した場合にその内容が報告されます。

def without_flag
  puts 'Starting some parallel work'
  thread =
      Thread.new do
        sleep 0.1
        fail 'something very bad happened!'
      end
  sleep 0.2
  puts 'Done!'
end
# フラグ無しで実行すると、エラーが報告されない
without_flag
#=> Starting some parallel work
#=> Done!

def with_flag
  # フラグにtrueをセットする
  Thread.report_on_exception = true

  puts 'Starting some parallel work'
  thread =
      Thread.new do
        sleep 0.1
        fail 'something very bad happened!'
      end
  sleep 0.2
  puts 'Done!'
end
# フラグありで実行すると、エラーが報告される
with_flag
#=> Starting some parallel work
#=> #<Thread:0x007fe1731faf20@(irb):66 run> terminated with exception:
#=> (irb):68:in `block in bar': something very bad happened! (RuntimeError)
#=> Done!

上記のコードは以下の記事を参考にしました。

New Features in Ruby 2.4 - BlockScore Blog

参考: Feature #6647: Exceptions raised in threads should be logged

デッドロック発生時に全スレッドのバックトレースが出力されるようになった

Ruby 2.4ではデッドロック発生時に全スレッドのバックトレースが出力されるようになりました。
以下はデッドロックが発生するコードの例です。

Thread.current.name = "MainThread!"
z = Thread.new{Thread.stop}
a, b = Thread.new { 1 until b; b.join }, Thread.new { 1 until a; a.join }
a.name = "aaaaa"
b.name = "bbbbb"
z.name = "zzzz"
a.join

上記のコードをRuby 2.4で実行すると、以下のように全スレッドのバックトレースが出力されます。

fatal: No live threads left. Deadlock?
4 threads, 4 sleeps current:0x007fd8c1c06b30 main thread:0x007fd8c1c06b30
* #<Thread:0x007fd8c207cce8@MainThread! sleep_forever>
   rb_thread_t:0x007fd8c1c06b30 native:0x007fff776aa000 int:0
   (irb):7:in `join'
   (irb):7:in `irb_binding'
   /Users/jit/.rbenv/versions/2.4.0-preview2/lib/ruby/2.4.0/irb/workspace.rb:87:in `eval'
   /Users/jit/.rbenv/versions/2.4.0-preview2/lib/ruby/2.4.0/irb/workspace.rb:87:in `evaluate'
   (省略)
* #<Thread:0x007fd8c3843070@zzzz@(irb):2 sleep_forever>
   rb_thread_t:0x007fd8c3029b10 native:0x00700000108000 int:0
   (irb):2:in `stop'
   (irb):2:in `block in irb_binding'
* #<Thread:0x007fd8c284a7e0@aaaaa@(irb):3 sleep_forever>
   rb_thread_t:0x007fd8c1f34170 native:0x0070000020b000 int:0
    depended by: tb_thread_id:0x007fd8c1c06b30
    depended by: tb_thread_id:0x007fd8c1de2060
   (irb):3:in `join'
   (irb):3:in `block in irb_binding'
* #<Thread:0x007fd8c284a6f0@bbbbb@(irb):3 sleep_forever>
   rb_thread_t:0x007fd8c1de2060 native:0x0070000030e000 int:0
    depended by: tb_thread_id:0x007fd8c1f34170
   (irb):3:in `join'
   (irb):3:in `block in irb_binding'

    from (irb):7:in `join'
    from (irb):7
    from /Users/jit/.rbenv/versions/2.4.0-preview2/bin/irb:11:in `<main>'

一方、Ruby 2.3で表示されるメッセージはこれだけでした。

fatal: No live threads left. Deadlock?
    from (irb):19:in `join'
    from (irb):19
    from /Users/jit/.rbenv/versions/2.3.1/bin/irb:11:in `<main>'

参考: Feature #8214: デッドロックチェックに全スレッドのバックトレースダンプの追加

Preview 2の主な変更点は以上です。続いてPreview 3の変更点を載せていきます。


文法上の変更点(これ以降はPreview 3とrc1)

rescue修飾子付きのコードを ( ) で囲んでメソッドの引数として渡せるようになった

以下のようなコードはこれまで構文エラーになっていましたが、Ruby 2.4では実行可能になりました。

# rescue修飾子付きのコードを ( ) で囲んでメソッドの引数として渡す
p (nil.split rescue nil)
#=> nil

なお、Ruby 2.3以前ではSyntaxError: syntax error, unexpected modifier_rescue, expecting ')'というエラーが出て実行できませんでした。

参考: Feature #12686: Allowing a postposed rescue in a method argument

後方互換性が失われる変更点

roundメソッドがデフォルトで偶数丸めをするようになった => halfオプションの追加のみになりました(rc1)

roundメソッドで四捨五入する際のルールが「偶数丸め」になりました。

preview3の時点では偶数丸めがデフォルトになっていましたが、その後の議論によりrc1ではデフォルトの挙動は従来通り(つまり0.5なら切り上げ)になりました。

Bug #12958: Breaking change in how `#round` works - Ruby trunk - Ruby Issue Tracking System

結果として、Ruby 2.4ではroundメソッドにhalf: :uphalf: :evenのオプションが追加されただけになります。
というわけで以下はrc1の仕様に準拠した解説です。

roundメソッドにhalfオプションが追加されました。
halfオプションには:up:evenの2種類の値が渡せます。

  • :up = 0.5は必ず切り上げ(デフォルト、2.3以前と同等)
  • :even = 偶数丸め(2.4から登場した新しい仕様)

偶数丸めは「最近接丸め」「JIS丸め」「ISO丸め」とも呼ばれ、「端数がちょうど0.5なら切り捨てと切り上げのうち結果が偶数となる方へ丸める」という端数処理の考え方です。

参考: 端数処理 - Wikipedia

以下はデフォルトのroundメソッドの結果と、half: :evenを指定した場合の挙動の違いです。

# デフォルト(0.5は必ず切り上げ)
12.5.round
# => 13
13.5.round
#=> 14

# 偶数丸め
12.5.round(half: :even)
#=> 12
13.5.round(half: :even)
#=> 14

なお、IntegerやRationalもroundメソッドでhalfオプションを渡すことができます。

# Ruby 2.4
125.round(-1, half: :even)
#=> 120
135.round(-1, half: :even)
#=> 140

(125r / 10).round(half: :even)
#=> 12
(135r / 10).round(half: :even)
#=> 14

参考:Bug #12548: Rounding modes inconsistency between round versus sprintf

参考:Bug #12958: Breaking change in how `#round` works - Ruby trunk - Ruby Issue Tracking System

パフォーマンス改善

ハッシュテーブルの高速化

こちらのアナウンスによると「従来Rubyのハッシュテーブルの内部実装 (st_table) ではに双方向連結リストとチェイン法を用いた実装が使われていましたが、挿入順の配列にオープンアドレス法を用いることによる高速化が行われました」とのことです。

以下のIssueではかなり専門的な議論が繰り広げられているので、興味がある方はチェックしてみてください。

参考: Feature #12142: Hash tables with open addressing

インスタンス変数へのアクセスの高速化

内部実装の改善により、インスタンス変数のアクセスが高速化したそうです。
以下のIssueでは10%ほど速くなった、というような話が載っています

参考: Bug #12274: accessing to instance variable should be fast.

irbの変更点

プログラムの実行中にirbが開けるbinding.irbメソッドの追加

Pryを使っているとbinding.pryというコマンドをコードに埋め込むことで、プログラムの実行中にPryを開くことができます。

これと同じように、irbでもbinding.irbというコマンドでirbが開けるようになりました。

# irbをrequireする
require 'irb'

s = 'hello'
puts s

# ここでirbを開く
binding.irb

puts 'bye'

上のコードを実行すると、次のように途中でirbが起動します。

Screen Shot 2016-11-16 at 08.37.18.png

参考:Revision 56624 - irb.rb: Binding#irb * lib/irb.rb (Binding#irb): new method like Binding#pry.

数値に関する新機能

infinite?/finite?メソッドがFloat以外の数値クラスにも追加された

値が無限大かどうか(+∞ or -∞)をチェックするためのinfinite?メソッド(逆はfinite?メソッド)が、Floatメソッドだけでなく、Integerクラス、Rationalクラス、Complexクラスにも追加されました。

def test_finite_infinite
  assert 1.finite?
  refute 1.infinite?

  assert 1.0.finite?
  refute 1.0.infinite?

  assert 1r.finite?
  refute 1r.infinite?

  assert Complex(1).finite?
  refute Complex(1).infinite?
end

参考:Feature #12039: Fixnum#infinite?/Bignum#infinite or Numeric#infinte, consistent with Float#infinite? and BigDecimal#infinite?

配列・ハッシュ・Setに関連する新機能

concatメソッドが複数の配列を引数に取れるようになった

配列のconcatメソッドが複数の配列を引数に取れるようになりました。

def test_array_concat
  assert [1, 2, 3, 4, 5, 6], [1, 2].concat([3, 4], [5, 6])
end

参考:Feature #12333: `String#concat`, `Array#concat`, `String#prepend` to take multiple arguments

ハッシュにcompact/compact!メソッドが追加された

値がnilの要素を削除するcompactメソッドがハッシュに追加されました。
破壊的に変更するcompact!メソッドも用意されています。

def test_hash_compact
  hash = { a: 1, b: nil, c: 2 }

  assert_equal({ a: 1, c: 2 }, hash.compact)
  assert_equal({ a: 1, b: nil, c: 2 }, hash)

  assert_equal({ a: 1, c: 2 }, hash.compact!)
  assert_equal({ a: 1, c: 2 }, hash)
end

ちなみに、これはActiveSupportで提供されているHash#compactメソッドと同等です。

http://api.rubyonrails.org/classes/Hash.html#method-i-compact

参考:Feature #12333: `String#concat`, `Array#concat`, `String#prepend` to take multiple arguments

ブロック無しのchunkがEnumeratorを返すようになった

Enumerableモジュールのchunkメソッドはこれまでブロックが必須でしたが、Ruby 2.4からはブロック無しで呼び出すとEnumeratorオブジェクトを返すようになります。

def test_numerable_chunk
  assert_instance_of Enumerator, [1, 2, 3].chunk
end

参考:Feature #2172: Enumerable#chunk with no block

Setに同じインスタンスかどうかで重複を判断するモードが追加された

Ruby 2.4ではSetクラスにcompare_by_identityメソッドが追加されました。
このメソッドを呼び出すと、同じインスタンスかどうか(つまりeql?ではなくequal?の結果)で重複を判断します。

また、compare_by_identity?メソッドを使うと、どちらのモードになっているのかを確認できます。

def test_set_compare_by_identity
  require 'set'

  set = Set.new
  values = ['a', 'a', 'b', 'b']
  set.merge(values)
  refute set.compare_by_identity?
  assert_equal ['a', 'b'], set.to_a

  set = Set.new
  values = ['a', 'a', 'b', 'b']
  set.compare_by_identity
  set.merge(values)
  assert set.compare_by_identity?
  assert_equal ['a', 'a', 'b', 'b'], set.to_a
end

文字列・シンボルに関連する新機能

concatメソッドとprependメソッドが複数の文字列を受け取れるようになった

Ruby 2.4では、concatメソッドとprependメソッドが複数の文字列を引数として受け取れるようになりました。

def test_string_concat
  s = 'a'
  assert_equal 'abc', s.concat('b', 'c')
  assert_equal 'abc', s

  s = 'z'
  assert_equal 'xyz', s.prepend('x', 'y')
  assert_equal 'xyz', s
end

参考:Feature #12333: `String#concat`, `Array#concat`, `String#prepend` to take multiple arguments

大文字小文字を無視して同一かどうかを返すcasecmp?メソッドが追加された(rc1)

Ruby 2.4では大文字小文字を無視して同一かどうかをtrue/falseで返すcasecmp?メソッドが追加されました。
このメソッドは文字列とシンボルで使えます。

def test_casecmp?
  assert 'abc'.casecmp?('ABC')
  refute 'abc'.casecmp?('DEF')

  assert :abc.casecmp?(:ABC)
  refute :abc.casecmp?(:DEF)
end

ちなみにRuby 2.3までは1, 0, -1を返すcasecmpメソッドで比較する必要がありました。

assert_equal 0, 'abc'.casecmp('ABC')
assert_equal -1, 'abc'.casecmp('DEF')
assert_equal 0, :abc.casecmp(:ABC)
assert_equal -1, :abc.casecmp(:DEF)

Feature #12786: String#casecmp? - Ruby trunk - Ruby Issue Tracking System

unpackの1要素目を返すunpack1メソッドが追加された(rc1)

Ruby 2.4ではunpackの戻り値から1要素目だけを返すunpack1メソッドが追加されました。

def test_unpack1
  # unpackは配列を返す
  assert_equal [65, 66, 67], "ABC".unpack("C*")

  # unpackは1要素目だけを返す
  assert_equal 65, "ABC".unpack1("C*")
end

Feature #12752: Unpacking a value from a binary requires additional '.first' - Ruby trunk - Ruby Issue Tracking System

ディレクトリやファイルに関連する新機能

Pathnameクラスにempty?メソッドが追加された

Ruby 2.4ではPathnameクラスにempty?メソッドが追加されました。
このメソッドを使うとディレクトリやファイルの中身が空かどうかを確認できます。

def test_pathname_empty?
  require 'tempfile'
  require 'pathname'

  Dir.mktmpdir do |dir|
    assert Pathname(dir).empty?
  end

  Tempfile.create("foo") do |f|
    assert Pathname(f).empty?
  end
end

なお、Preview 2ではDirクラスとFileクラスにもempty?メソッドが追加されています。

参考:Feature #12596: Add Pathname#empty? to be consistent with Dir.empty? and File.empty?

テキストファイルを行単位で読み込む際に、chompするかどうかを指定できるようになった

IO#gets, IO#readline, IO#each_line, IO#readlines, IO#foreachchompオプションを指定できるようになりました。
このオプションを指定すると、最初からchompした状態(つまり改行文字を削除した状態)で各行のテキストを取得できます。

def test_io_readlines
  require 'tempfile'
  tf = Tempfile.new("foo")
  tf.print "abc\ndef\nghi\n"
  tf.close

  File.open(tf.path) do |f|
    # chompせずに読み込む
    assert_equal ["abc\n", "def\n", "ghi\n"], f.readlines

    f.rewind

    # chompして読み込む
    assert_equal ["abc", "def", "ghi"], f.readlines(chomp: true)
  end
ensure
  tf&.unlink
end

Refinementsに関連する新機能

モジュールもrefineできるようになった

Ruby 2.4ではクラスだけでなく、モジュールに対してもrefineできるようになりました。

# refineされるモジュール
module Awesome
  def xxx
    'awesome'
  end
end

# モジュールをrefineするモジュール
module SuperAwesome
  refine Awesome do
    def xxx
      # superは元のモジュールのメソッドを呼び出す
      "not #{super} but super-awesome"
    end
  end
end

using SuperAwesome

class Person
  include Awesome

  def say
    "I am #{xxx}!"
  end
end

class RefinementsTest < Minitest::Test
  def test_module_refinements
    person = Person.new
    assert_equal 'I am not awesome but super-awesome!', person.say
  end
end

参考:Feature #12534: Refinements: refine modules as well

"&:to_s"のようにシンボルをProc化した場合もrefineされるようになった

Ruby 2.4では&:to_sのようにシンボルをProc化した場合もrefineされるようになりました。

module StringPugs
  refine String do
    def pugs
      "Pugs!"
    end
  end
end

using StringPugs

class RefinementsTest < Minitest::Test
  def test_symbol_to_proc
    assert_equal ['Pugs!', 'Pugs!'], ['a', 'b'].map(&:pugs)
  end
end

参考:Feature #9451: Refinements and unary & (to_proc)

sendメソッド経由で呼びだしたメソッドにもrefineされるようになった

Ruby 2.4ではsendメソッド経由で呼びだしたメソッドもrefineされるようになりました。

require 'minitest/autorun'

module StringPugs
  refine String do
    def pugs
      "Pugs!"
    end
  end
end

using StringPugs

class RefinementsTest < Minitest::Test
  def test_send
    assert_equal 'Pugs!', 'a'.send(:pugs)
  end
end

参考:Feature #11476: Methods defined in Refinements cannot be called via send

Refinementsで使われているモジュールを取得するModule.used_modulesメソッドが追加された

Ruby 2.4では現在のスコープのRefinementsで使われているモジュールを取得するModule.used_modulesメソッドが追加されました。

module StringPugs
  refine String do
    def pugs
      "Pugs!"
    end
  end
end

# Refinementsで使った1つ目のモジュール
using StringPugs

module Awesome
  def xxx
    'awesome'
  end
end

module SuperAwesome
  refine Awesome do
    def xxx
      "not #{super} but super-awesome"
    end
  end
end

# Refinementsで使った2つ目のモジュール
using SuperAwesome

class Person
  include Awesome

  def say
    "I am #{xxx}!"
  end
end

class RefinementsTest < Minitest::Test
  def test_used_modules
    assert_equal [StringPugs, SuperAwesome], Module.used_modules
  end
end

参考:Feature #7418: Kernel#used_refinements

その他

警告発生時の振る舞いをカスタマイズできるようになった

Ruby 2.4ではWarningというモジュールが組み込みライブラリに追加されました。
このモジュールにはwarnというクラスメソッドを持っています。
Rubyの実行中に警告が出力された際はこのWarning.warnメソッドが呼び出されるので、このメソッドをオーバーライドすると、警告の出力を取得したり加工したりすることができます。

以下は発生した警告を配列として保持し、後から取得するコード例です。

module Warning
  # これはRuby 2.4で追加されたメソッドのオーバーライド
  def self.warn(msg)
    warnings << msg
    super
  end

  # これは独自に追加したクラスメソッド
  def self.warnings
    @warnings ||= []
  end
end

class WarningTest < Minitest::Test
  def setup
    Warning.warnings.clear
  end

  def test_warn
    # 警告を発生させるため、わざとFixnumクラスを参照する
    assert Fixnum == Integer

    # 出力された警告をちゃんと保持しているかどうかの検証
    assert_equal 1, Warning.warnings.size
    msg = Warning.warnings.first
    assert msg.include?('warning: constant ::Fixnum is deprecated')
  end
end

ちなみに、Warningというクラス名は普通の英単語なので、みなさん自身が既存のコードで独自に定義している可能性もあります。
既存のコードでWarningというクラスを定義していると、Ruby 2.4にアップデートしたタイミングで名前の衝突が起きてエラーになるので注意してください。

# Ruby 2.4
class Warning
end
#=> TypeError: Warning is not a class

参考:Feature #12299: Add Warning module for customized warning handling

Shellwords.shellsplitがバックスラッシュを正しく解釈するようになった

Shellwords.shellsplitメソッドが引数に渡された文字列のバックスラッシュを正しく解釈するようになりました。

# Ruby 2.3
require 'shellwords'

# この結果はおかしい
Shellwords.shellsplit(%q|printf "%s\\n"|)
#=> ["printf", "%sn"]

# これはOK
Shellwords.shellsplit(%q|printf '%s\\n'|)
#=> ["printf", "%s\\n"]
# Ruby 2.4
require 'shellwords'

# どちらもOK
Shellwords.shellsplit(%q|printf "%s\\n"|)
#=> ["printf", "%s\\n"]

Shellwords.shellsplit(%q|printf '%s\\n'|)
#=> ["printf", "%s\\n"]

参考:Bug #10055: Shellwords.shellsplit() does not match POSIX sh behavior for backslashes within double-quoted strings

IPAddrオブジェクトを==で比較する際、右辺に無効なオブジェクトが来てもエラーが起きなくなった

IPAddrクラスのオブジェクトを==で比較する際、右辺に単純な文字列等を置いたりするとエラーが起きていましたが、Ruby 2.4ではエラーではなくfalseを返すようになりました。

def test_ipaddr_compare
  require 'ipaddr'
  # Ruby 2.3以前ではIPAddr::InvalidAddressErrorが発生していた
  refute IPAddr.new("1.1.1.1") == "sometext"
end

RDocのバージョンが5.0.0に上がった

Ruby 2.4にバンドルされるRDocのバージョンが5.0.0に上がりました。

参考:rdoc-5.0.0 をリリースした - HsbtDiary

XMLRPCが外部gemとして切り出された

Ruby 2.4ではXMLRPCが標準ライブラリではなく、外部のgemになりました。

https://github.com/ruby/xmlrpc

ただし、Rubyインストール時にXMLRPC gemも一緒にインストールされるため、通常の使用では特に変化はありません。

参考:Feature #12160: Extract XMLRPC library to bundled gem

FreeBSD 4未満はサポートされなくなった

FreeBSD 4未満(FreeBSD < 4)はサポートされなくなったとのことです。

今回確認しきれなかったPreview 3の変更点

以下の変更点は今回自分で確認できなかったので、こちらのブログに書かれている記載内容を引用させてもらいます。

TracePoint#callee_id [Feature #12747]

r56592 の続きで TracePoint に callee_id を追加しています。 alias されたメソッドを呼んだ時に呼び元で呼んだメソッド名のほうを返すようにしています。実体のメソッド名(method_id)と呼び元でのメソッド名(callee_id)を明確に区別するようにしたのですね。

http://d.hatena.ne.jp/nagachika/20161106

New method: Net::HTTP.post [Feature #12375]

標準添付ライブラリ net/http に POST のリクエストを送信する Net::HTTP.post メソッドを追加しています。 Net::HTTP.post_form というメソッドもありますが、こちらはパラメータを application/x-www-form-urlencoded でエンコードする前提ですが、こちらはリクエストボディをそのまま指定するので JSON エンコードしたリクエストボディを渡すこともできます。また header の指定もできるようになっているので、より便利になりました。

http://d.hatena.ne.jp/nagachika/20161106

Readline.quoting_detection_proc and Readline.quoting_detection_proc= [Feature #12659]

拡張ライブラリ readline で Readline の rl_char_is_quoted_p というコールバック関数を登録する変数に対応して Readline.quoting_detection_proc= で call メソッドを呼べるオブジェクトを登録できるようにしています。これは入力する文字がクオート内でエスケープされているかどうかを判定するものだそうです。

http://d.hatena.ne.jp/nagachika/20161003

CGI WEBrick Don't allow , as a separator [Bug #12791]

標準添付ライブラリ webrick と cgi で HTTP ヘッダの Cookie をパースする時に複数の Cookie を "," で区切って並べる記法を受け付けないようにしています。

http://d.hatena.ne.jp/nagachika/20160927

RubyVM::Env was removed.

ユーザは直接使ってなかっただろうと思いますが RubyVM::Env クラスは削除して、内部的なオブジェクトをまとめている T_IMEMO オブジェクトの仲間入りをしています。

http://d.hatena.ne.jp/nagachika/20160728

Support CLOCK_MONOTONIC_RAW_APPROX, CLOCK_UPTIME_RAW, and CLOCK_UPTIME_RAW_APPROX which are introduced by macOS 10.12.

macOS 10.12 (sierra) で clock_gettime() が対応されたので Process.clock_gettime の clock_id 用の各定数のサポートするプラットフォームへ macOS 10.12 を追記しています。 さらに新たに追加されるフラグとして Process に定数 CLOCK_MONOTONIC_RAW_APPROX、CLOCK_UPTIME_RAW、CLOCK_UPTIME_RAW_APPROX を追加しています。

http://d.hatena.ne.jp/nagachika/20160921

the extension library is removed. Till 2.0 it was a pure ruby script "thread.rb", which has precedence over "thread.so", and has been provided in $LOADED_FEATURES since 2.1.

(こちらは該当する情報が確認できませんでした)

まとめ

というわけで、本記事ではRuby 2.4の新機能や変更点をまとめてみました。

個人的に一番気になるのはFixnumとBignumの統合でしょうか。
普段自分が書いているコードではわざわざ両者を区別するようなコードは書いていませんでしたが、もしかするとよく使うgemでそういったコードが書いてあると動かなくなるかも?と思ったりします。
とはいえ、FixnumやBignumを定数として参照した場合はIntegerが返るようになっているので、Ruby 2.4に変えた瞬間に動かなくなる、というケースはほとんどないような気もします。

その他はそこまでびっくりするような新機能が追加されているわけではありませんが、Enumerableモジュールのsumメソッドや、小数点を指定できるceil/floorなど、派手さはないが実用性のある新機能が追加されている印象です。

さあ、みなさんもRuby 2.4をインストールして、12月25日の正式リリースまでに新機能をいろいろ試してみてください!

あわせて読みたい

Ruby 2.3の新機能は以下の記事でまとめてあります。こちらもあわせてどうぞ。

サンプルコードでわかる!Ruby 2.3の主な新機能 - Qiita

本文には何度か正規表現の話が出てきましたが、そもそも正規表現って何?という方はこちらの記事を読んでみてください。

初心者歓迎!手と目で覚える正規表現入門・その1「さまざまな形式の電話番号を検索しよう」 - Qiita

Ruby 2.4ではTimeとDateTimeの不具合が修正されましたが、TimeとDateTimeって何が違うの?という方はこちらの記事が参考になるかもしれません。

RubyとRailsにおけるTime, Date, DateTime, TimeWithZoneの違い - Qiita

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.