What's new in Ruby 2.6
Ruby 2.6 was released on Christmas day, and brought a few interesting new features. Here’s a quick summary of what’s changed. As with my summaries of previous versions (2.5, 2.4, 2.3) I’m only summarizing features that I find most interesting. For a complete list of changes, take a look at the changelog.
Endless ranges
A new syntax was introduced to represent an endless range. This will be useful when matching ranges in case statements.
case n
when 1..9 then 'Single digit'
when 10..99 then 'Two digit'
when 100.. then 'Three or more'
end
Previously, (1..)
would be represented as (1..Float::INFINITY)
.
Not many people are familiar that you can
represent infinity like this,
so this syntax is a definite improvement.
Composing procs, lambdas and methods
#>>
and #<<
methods were added to Proc
and Method
objects
to allow composing them into a new method.
square = -> (n) { n * n }
add_2 = -> (n) { n + 2 }
(square >> add_2).call(5) #=> 27
(square << add_2).call(5) #=> 49
With the #>>
operation,
the number gets square
d first and then add_2
is performed
on the result of the first proc call.
In case of #<<
,
the order in which the procs are called is reversed.
I wish we didn’t have
two different ways of composing procs,
and only had #>>
.
But composition is an important addition to Ruby
that will allow us to write in a more functional style.
Kernel#then
This isn’t a new feature,
but an alias for Kernel#yield_self
which was introduced in Ruby 2.5,
and allows us to chain operations
into pipelines like this:
isbn = '978-1-93778-549-9'
isbn.gsub('-', '')
.then { |isbn| URI("#{API_URL}?q=isbn:#{isbn}") }
.then { |uri| Net:HTTP.get(uri) }
.then { |json_response| JSON.parse(json_response) }
.then { |response| response.dig('items', 'volumeInfo') }
(Shameless plug: I recorded a guest episode on RubyTapas showing a series of refactors leading to the code above - watch it here.)
Enumerable#chain
and Enumerator#+
This returns an Enumerator::Chain
object,
which works as a single enumerator.
(1..3).chain((5..7), [9, 10]).to_a
#=> [1, 2, 3, 5, 6, 7, 9, 10]
list = (1..3).each + (5..7) + (9..10)
list.to_a
#=> [1, 2, 3, 5, 6, 7, 9, 10]
Merge multiple hashes
Hash#merge
only took one argument till 2.5.
The only way to merge more than two hashes
was to chain calls to #merge
.
You can now pass multiple hashes to the method.
foo = { a: 1 }
bar = { b: 2 }
baz = { c: 3 }
foo.merge(bar, baz) #=> { a: 1, b: 2, c: 3 }
# ruby 2.5
# foo.merge(bar).merge(baz)
Array#union
and Array#difference
These methods work just like
the |
and &
operators.
The difference is that they can accept multiple arguments
and are easier to chain.
a = [1, 1, 2, 3]
b = [3, 4]
a.union(b) #=> [1, 2, 3, 4]
a.difference(b) #=> [1, 1, 2]
Enumerable#to_h
with block
Enumerable#to_h
now accepts a block that maps keys to values.
This allows us to transform hashes
without creating an intermediate array using map
,
or using the harder-to-read reduce
syntax.
hash = { foo: 2, bar: 3 }
hash.to_h { |k, v| [k.upcase, v*v] } #=> { FOO: 4, BAR: 9 }
# ruby 2.5:
# hash.map { |k, v| [k.upcase, v*v] }.to_h
# hash.reduce({}) { |result, (k, v)| result.merge(k.upcase => v) }
Enumerator::ArithmeticSequence
This subclass of Enumerator
was introduced to represent
objects created by calling step
on Range
and Numeric
objects.
This makes it possible to do things like
check equality of sequences.
r1 = (1..10).step(2)
r2 = (1..10).step(2)
r1 == r2 #=> true if 2.6, false in 2.5
1.step == (1..).step #=> true
Changes in Range
behavior
The %
operator
has been added as an alias to step
.
So (1..10) % 2
is equivalent to (1..10).step(2)
.
Range#===
now uses uses #cover?
instead of #include?
.
case
statements internally use ===
,
so this could lead to subtly different behavior
in case statements.
(a..b).cover?(x)
checks if a <= x < b
,
whereas (a..b).include?(x)
iterates through the range
and checks if any element equals x
.
This allows us to do things like:
case DateTime.now
when Date.today..Date.today+1
puts 'matched'
else
puts 'not matched'
end
Note: In a previous version of this article, I used a string range in the example. For backwards compatibility, the behavior of string ranges hasn’t changed.
('a'..'c').cover?('bb') #=> true
('a'..'c').include?('bb') #=> false
case 'bb'
when 'a'..'c' then 'matched'
else 'not matched'
end
#=> 'not matched'
exception
keyword argument for Kernel
methods
An exception
keyword argument
was added for some Kernel
methods
like Integer
, Float
and system
.
For the Numeric
methods,
we can now pass exception: false
to avoid raising when parsing an invalid value,
and to instead return nil
.
Kernel#system
, on the other hand,
accepts exception: true
to raise
if the command exits with non-zero exit status
(instead of returning false
)
or if command execution fails
(instead of returning nil
).
Integer('foo', exception: false) #=> nil
Float('foo', exception: false) #=> nil
system('foo', exception: true) #=> exception
Performance improvements
The biggest news on the performance front
is that a JIT implementation,
called
MJIT,
was merged into Ruby.
This can be enabled
using the --jit
flag.
MJIT has led to speedups in micro-benchmarks, but isn’t mature enough yet to work for larger codebases like Rails apps. It is even slower for Rails than the non-JIT version.
However, this is an important step towards the Ruby 3x3 goal, which is to make Ruby 3 at least 3x faster that Ruby 2.0. Currently Ruby 2.6 with JIT is nearly 2.5x faster than 2.0 on the Optcarrot benchmark, which is used to compare the performance of Ruby releases for the 3x3 goal.
Aside from this,
a second garbage collection heap,
called Transient Heap,
was introduced,
which reduces memory usage
and improves GC speed
of short lived objects.
Proc.call
and block.call
Better introspection
Ruby’s introspection abilities have been improved,
which the introduction
of the RubyVM::AbstractSyntaxTree
class,
which lets us parse Ruby code into AST.
Binding.source_location
was also added,
which returns an array containing
the file name and the line number
where it was called.
Other changes
- The flip flop operator is finally deprecated, and will probably be removed in Ruby 3.
- Bundler is now a default gem,
so you no longer have to do
gem install bundler
after installing Ruby. Rubygems 3.0.1 was also merged into Ruby. Array#filter
was added as an alias for#select
.- Previous versions of ruby warned
if you had an
else
clause in abegin
block without arescue
. This will cause a syntax error in 2.6.
begin
puts 'begin'
else
puts 'else'
end
# Ruby 2.6
#=> foo.rb:5: else without rescue is useless
# Ruby 2.5
#=> foo.rb:7: warning: else without rescue is useless
#=> begin
#=> else
Links
- NEWS for 2.6.0
- Ruby 2.6 changes list by Victor Shepelev, has a comprehensive list of changes, along with links to the relevant discussions on the Ruby issue tracker.