What's new in Ruby 2.7
Ever since Ruby 2.1.0, the Ruby core team have released a new version of Ruby every Christmas, and this time we got 2.7.0. This will likely be the last 2.x release of Ruby, because Ruby 3.0 is expected to ship next Christmas.
Here’s a quick summary of some of the most interesting features in this release. (For previous versions, see: 2.6, 2.5, 2.4, 2.3).
Pattern matching
A new pattern matching syntax was introduced, which allows you to write case statements like this:
def test_pattern(value)
case value
in 0
'Zero'
in Integer if value < 0
'Negative'
in Integer => n
"Number #{n}"
in { value: x }
"Hash value: #{x}"
in _
'Unknown'
end
end
test_pattern(-1) #=> 'Negative'
test_pattern(0) #=> 'Zero'
test_pattern(2) #=> 'Number: 2'
test_pattern({ value: 3 }) #=> 'Hash value: 3'
test_pattern('four') #=> 'Unknown'
Startless ranges
In 2.6, we got end-less ranges, and this time we will have start-less ranges. This will allow syntax like:
case n
when ..0 then 'Negative'
when 1..9 then 'Single digit'
when 10.. then 'Two or more'
end
Another interesting use of this will be the ability to use this in DSLs. For instance, we could do this in ActiveRecord:
Task.where(due_on: ..Date.today)
Numbered parametes
A new syntax was added to add numbered parameters to blocks.
(1..3).map { _1 * _1 } #=> [1, 4, 9]
hash = { a: 2, b: 3 }
hash.map { "#{_1} = #{_2}" } #=> ["a = 1", "b = 2"]
add_3 = -> { _1 + _2 + _3 }
add_3.call(3, 5, 7) #=> 15
These are contrived examples, but so far I’m not convinced it was worth adding this new syntax, and I don’t see myself using this over explicit arguments.
New Enumerable
and Array
methods
Enumerable#tally
counts the occurences of each item in an enumerable,
and returns it as a hash.
strings = ['a', 'a', 'a', 'b', 'c']
strings.tally
#=> { 'a' => 3, 'b' => 1, 'c' => 1 }
# in 2.6, we'd do it like this:
strings.each_with_object({}) do |value, result|
result[value] ||= 0
result[value] += 1
end
Enumerable#filter_map
combines select
and map
into a single block,
avoiding the need for an intermediate array allocation.
# squares of odd numbers in 1..10
(1..10).filter_map { |x| x*x if x.odd? } #=> [1, 9, 25, 49, 81]
# ruby 2.6
(1..10).select(&:odd?).map { |x| x*x }
Array#intersection
is the equivalent of calling &
on arrays,
but allows multiple arguments.
[1,2,3,4,5].intersection([2,3,4], [2,4,5]) #=> [2, 4]
Enumerator::Lazy#eager
converts a lazy enumerator into a non-lazy one.
(1..).lazy
.select(&:odd?) # lazy
.take(3) # lazy
.eager # next operation will be non-lazy
.map { |x| x * x } #=> [1, 9, 25]
Argument forwarding syntax
New syntax (...
) has been added to allow
forwarding all the arguments of a method.
def add(a, b)
a + b
end
def add_and_double(...)
add(...) * 2
end
add_and_double(2, 3) #=> 10
Deprecation behavior in keyword arguments
In previous versions of Ruby, if you had a method with keyword arguments, and it was called with a Hash as the last argument, the hash would have been converted to keyword args. However, this has been deprecated with a view to remove this behavior in 3.0. This will now cause a warning.
def foo(key: 1)
key
end
foo({ key: 2 })
#=> 43
# warning: The last argument is used as the keyword parameter
# warning: for `foo' defined here
The splat operator can be used to convert a hash to keyword args.
foo(**{ key: 2 })
Compaction GC
The Ruby garbage collector
has seen continual improvements with every release,
and this time a compactor has been introduced
which defragments the heap
and helps reduce memory usage.
A GC.compact
method was also introduced,
to allow manually triggering compaction.
IRB improvements
There are a lot of improvements to IRB, with features such as multiline editing, rdoc integration to show documentation in IRB and colorized output.
Although I’m happy to see improvements here, IRB still lags far behind pry, and I don’t see myself using it much until it catches up.
Calling a private method on self
The behavior of private
has changed slightly.
Previously, if you called a private method on self
,
(eg. self.foo
),
it would have been a NoMethodError
because private methods can only be called
on the implicit receiver.
This has changed so that
calling a private method on self
no longer raises
This was previously only possible
for private attr_accessor
s,
but now has been extended to all private methods.
What features are we not getting yet?
Aside from all the new features that were added, this version is also interesting for some features that didn’t make the cut. Here’s a few that were introduced as experimental features but reverted before the final release.
The pipeline operator was introduced, but reverted based on the community’s feedback.
# with pipeline
foo
|> bar 1, 2
|> display
# normal method chaining
foo
.bar(1, 2)
.display
I felt that it was an unnecessary addition
because it looks like Elixir’s pipeline operator,
but effectively works like a method call.
In the above example,
the only difference is that
the pipeline (|>
) version
doesn’t require parentheses around bar
’s arguments,
whereas it is required for the normal method call.
A method reference operator was introduced
and subsequently removed.
This would have allowed writing
File.method(:read)
as File.:read
.
An example that shows
how this would have
provided another way to chain operations:
some_uri
.then(&Net::HTTP.:get)
.then(&JSON.:parse)
This was reverted because Matz and the core team wanted to rethink the broader design around how functional programming features would work in Ruby.
Immutable strings are not going to be the default yet.
When the frozen_string_literal: true
magic comment
was introduced in Ruby 2.3,
the plan was to make string literals
immutable by default in Ruby 3.0.
However, Matz has reluctantly abandoned the idea
in order to avoid compatibility issues with gems,
and to make the transition to Ruby 3.0 easier.
And finally, a not about the notorious flip flop operator. This confusing feature was deprecated in 2.6, with hopes that it would be removed in 3.0. However, after a few requests from the community, Matz has decided to revert the deprecation, so it’s not going away in the foreseeable future.