My Favorite Features in Ruby 2.7
January 20, 2020
Every Christmas Day the Ruby core team releases a new version of Ruby and this past December 25th was no exception when the team made Ruby 2.7 available. I finally found some time to upgrade and here are some of my favorite new features of the language and IRB.
Upgrading to Ruby 2.7
I use rbenv to maintain my Ruby environment and Homebrew as a package manager for macOS so upgrading is as easy as running these two commands:
$ brew upgrade rbenv ruby-build
$ rbenv install 2.7.0
IRB Updates
The first thing you’ll notice after upgrading is that IRB looks a lot different thanks to syntax highlighting. The colored variable names, methods, and conditional operators make it much easier to visually parse code than it was in the black-and-white days.
Ruby 2.7 also has auto indentation so methods and conditional code automatically align as you type. They’ve also added auto complete so you can quickly tab out classes and methods. But my favorite feature is probably being able to load a whole method definition or class simply by pressing the up arrow key once. Prior to 2.7 it often took dozens of up arrow keypresses to load the code you were looking for:
Array#intersection
#intersection
is called on an array and takes other arrays as arguments. It returns another array that contains only the values that are common to all of the arrays. The order of the elements in the returned array is preserved from the array #intersection
was called on.
array1 = [3,4,2,6,7,12,1]
array2 = [5,8,3,2,1,5,14]
array3 = [6,2,9,3,5,23,1]
array1.intersection(array2, array3)
=> [3, 2, 1]
Prior to 2.7 you could call array1 & array2 & array3
to get the same result but I think having a method that takes arguments is more readable. In terms of performance, the #intersection
method is on par with array &
.
# macOS 10.14.4 2.6 GHz i5
array1 = Array.new(1000) { rand(1...99) }
array2 = Array.new(1000) { rand(1...99) }
array3 = Array.new(1000) { rand(1...99) }
Benchmark.bmbm do |x|
x.report("array_&") {50000.times { array1 & array2 & array3 }}
x.report("intersection") {50000.times { array1.intersection(array2, array3) }}
end
user system total real
array_& 3.483466 0.007528 3.490994 (3.499241)
intersection 3.551033 0.015492 3.566525 (3.576167)
Enumerable#filter_map
#filter_map
runs a block once on each element in an enumerable, simplifying the process of generating mapped arrays. In older versions of Ruby, a common approach to mapping arrays was to combine #map
with #select
or #compact
. Not only is the new #filter_map
method faster than the other approaches it’s also less verbose and easier to read.
# macOS 10.14.4 2.6 GHz i5
enum = 1.upto(1_000)
Benchmark.bmbm do |x|
x.report("select + map") { 50000.times {
enum.select {|i| i % 3 == 0}.map{|i| i +1 }}}
x.report("map + compact") { 50000.times {
enum.map {|i| i + 1 if i % 3 == 0}.compact }}
x.report("filter_map") { 50000.times {
enum.filter_map {|i| i + 1 if i % 3 == 0 }}}
end
user system total real
select + map 5.655839 0.014081 5.669920 (5.684055)
map + compact 5.561898 0.039747 5.601645 (5.639864)
filter_map 5.043501 0.014836 5.058337 (5.080673)
Enumerable#tally
#tally
counts how many times the same elements appear in a collection and returns a hash where the keys are the elements from the original collection and the values are how many times each element occurs:
[1,2,45,3,2,1,1,1,2,3,45,2].tally
=> {1=>4, 2=>4, 45=>2, 3=>2}
Prior to 2.7 you had to do something like this:
[1,2,45,3,2,1,1,1,2,3,45,2].group_by { |i| i }.transform_values(&:size)
=> {1=>4, 2=>4, 45=>2, 3=>2}
Along with being much easier to read, #tally
appears to be significantly more performant than combining #group_by
and #transform_values
:
# macOS 10.14.4 2.6 GHz i5
array = Array.new(1000) { rand(1...99) }
Benchmark.bmbm do |x|
x.report("group_by + trans_vals") { 50000.times {
array.group_by { |i| i }.transform_values(&:size) }}
x.report("tally") { 50000.times { array.tally }}
end
user system total real
group_by + trans_vals 6.030849 0.012908 6.043757 (6.062187)
tally 3.374915 0.010697 3.385612 (3.396156)
Wrapping Up
The new methods and updates to IRB in Ruby 2.7 share a common theme: they continue to make Ruby a lot of fun to write without sacrificing performance. Sure, the methods I mentioned above contain some magic by abstracting some details from the programmer, but that’s just fine to me if it helps me deliver value to customers while enhacing my passion for the process.
Hi, I’m Ryan McMahon—a software developer who lives and works in Buffalo, NY. I build things for the web using React, Ruby, Rails, and .NET. Connect with me on Twitter.