Changes in Ruby 2.4.0-preview1 by examples
Ruby 2.4.0-preview1 has been released recently. I have had a quick look at the changes and done some experiments myself. This post will demonstrate those changes by examples so it will be easier to adapt Ruby 2.4.
Versions used for comparison are Ruby 2.4.0-preview1 and Ruby 2.3.0.
Fixnum and Bignum are unified into Integer
Reference: Feature #12005
Ruby 2.3
Integer
is just an abstract class- Every integer is an instance of either
Fixnum
orBignum
# Ruby 2.3
3218489498465156146545613521651651651651657.class
=> Bignum
69.class
=> Fixnum
Ruby 2.4
Integer
is a concrete classFixnum
andBignum
are removed and now is an alias name ofInteger
- Every integer is an instance of
Integer
# Ruby 2.3
3218489498465156146545613521651651651651657.class
=> Integer
69.class
=> Integer
Fixnum
=> Integer
Bignum
=> Integer
Compatibility Issues
Since Fixnum
and Bignum
are now alias of Integer
, I found this interesting case:
# Ruby 2.3
69.is_a? Bignum
=> false
# Ruby 2.4
69.is_a? Bignum
=> true
Float/Integer#ceil/floor/truncate now take an optional digits
Reference: Feature #12245
# Ruby 2.3
0.12342352.floor 2
0.12342352.ceil 2
0.12342352.truncate 3
=> ArgumentError: wrong number of arguments (given 1, expected 0)
# Ruby 2.4
0.12342352.floor 2
=> 0.12
0.12342352.ceil 2
=> 0.13
0.12342352.truncate 3
=> 0.123
Multiple assignment in conditional expression
Reference: Feature #10617
# Ruby 2.3
puts a + b if (a, b = 1, 2)
=> SyntaxError: multiple assignment in conditional
# Ruby 2.4
puts a + b if (a, b = 1, 2)
=> warning: found = in conditional, should be ==
=> 3
String/Symbol supports Unicode case mappings
String/Symbol#upcase/downcase/swapcase/capitalize(!)
now work with Unicode characters.
Reference: Feature #10085
Ruby 2.3
"Tiếng Việt".upcase
=> "TIếNG VIệT"
"Tiếng Việt".swapcase
=> "tIếNG vIệT"
"TIẾNG VIỆT".downcase
=> "tiẾng viỆt"
"ê".capitalize
=> "ê"
Ruby 2.4
"Tiếng Việt".upcase
=> "TIẾNG VIỆT"
"Tiếng Việt".swapcase
=> "tIẾNG vIỆT"
"TIẾNG VIỆT".downcase
=> "tiếng việt"
"ê".capitalize
=> "Ê"
String.new(capacity: size)
New option when creating string: capacity: size
. This allows the created string growing without the need of recalculating space for that string. There are not many cases where we actually use this.
Reference: Feature #12024
s = String.new capacity: 10000
=> ""
s = String.new "test", capacity: 10000
=> "test"
Symbol#match now returns MatchData
Reference: Bug #11991
# Ruby 2.3
:abc.match /abc/
=> 0
# Ruby 2.4
:abc.match /abc/
=> #<MatchData "abc">
Array#min/max now faster and independent from Enumerable#min/max
Array#min/max
are now defined in Array
class instead of being included from Enumerable
. Hence when Array#min/max
is invoked, call to #each
is skipped and makes the call faster (10+ times in ticket benchmark, see reference).
Reference: Feature #12172
A notable consequence is that redefining Enumerable#min/max
does not affect Array#min/max
anymore. This may lead to incompatible in some of Ruby libs out there. Enumerator
is not affected by this change though.
In Ruby 2.3
[1, 2.5, -3].min
=> -3
[1, 2.5, -3].max
=> 2.5
module Enumerable
def min
'min'
end
def max
'max'
end
end
[1, 2.5, -3].min
=> "min"
[1, 2.5, -3].max
=> "max"
In Ruby 2.4
[1, 2.5, -3].min
=> -3
[1, 2.5, -3].max
=> 2.5
module Enumerable
def min
'min'
end
def max
'max'
end
end
[1, 2.5, -3].min
=> -3
[1, 2.5, -3].max
=> 2.5
[1, 2.5, -3].to_enum.min
=> "min"
[1, 2.5, -3].to_enum.max
=> "max"
Another noteable change to Array#min/max
is that under certain conditions,[x, y].max
and [x, y].min
are optimized so that a temporal array is not created. This optiomization is lost when Array#min/max
are redefined.
Enumerable#sum & Array#sum
#sum
is added to Enumerable
hence Enumerator
, Range
, Hash
and Array
should have #sum
method.
#sum
only work with numeric elements, and could receive a block.
Reference: Feature #12217
[1, 2, 3].sum
=> 6
[1, 2, 3].to_enum.sum
=> 6
(1..3).sum
=> 6
[1, 2, 3].sum { |i| i * i }
=> 14
[1, "2", 3].sum
=> TypeError: String can't be coerced into Integer
Hash#sum
always need a block to proceed.
{ 3 => 5, 6 => 7}.sum
=> TypeError: Array can't be coerced into Integer
{ 3 => 5, 6 => 7}.sum { |k,v| k * v }
=> 57
Similar to #min/max
, Array#sum
is implemented independently from Enumerable
.
module Enumerable
def sum
'sum'
end
end
[1, 2, 3].sum
=> 6
(1..3).sum
=> "sum"
[1, 2, 3].to_enum.sum
=> "sum"
Note: beware of float
[0.1, 0.2].sum
=> 0.30000000000000004
Dir.empty? & File.empty?
Quickly check if a directory or a file is empty.
References: Feature #10121, Feature #9969
File.empty? "test.csv"
=> false
Dir.empty? "test"
=> true
Regexp#match?
Reference: Feature #8110
/abc/.match? 'abc'
=> true
Thread#report_on_exception and Thread.report_on_exception
I have not fully understood these functions yet. Follow is the example I guessed from feature discussions & description.
Reference: Feature #6647
# without Thread.report_on_exception
Thread.new { 1 / 0 }
=> #<Thread:0x000000020aadc8@(irb):1 run>
# with Thread.report_on_exception
Thread.report_on_exception = true
=> true
Thread.new { 1 / 0 }
#<Thread:0x0000000208b298@(irb):3 run> terminated with exception:
(irb):3:in `/': divided by 0 (ZeroDivisionError)
from (irb):3:in `block in irb_binding'
=> #<Thread:0x0000000208b298@(irb):3 dead>
MatchData#named_captures/values_at
MatchData#named_captures
allows matchdata returned as a hash and MatchData#values_at
to support #named_captures
.
References: Feature #11999, Feature #9179
'12'.match(/(?<a>.)(?<b>.)(?<c>.)?/).named_captures
=> {"a"=>"1", "b"=>"2", "c"=>nil}
'12'.match(/(?<a>.)(?<b>.)(?<c>.)?/).values_at 'a'
=> ["1"]
Final note
I really enjoy the changes in Ruby 2.4 and have a good time writing this post. I hope you have good time reading this too.