Whether you celebrate Christmas or not, it is hard to miss the classic song by Mariah Carey, "All I Want For Christmas Is You" playing in department stores, on the radio or practically anywhere you venture to during this time. For enthusiasts of the Ruby language through this time of year has a whole different meaning, because Christmas is when we can expect a new major release of Ruby. This year is no exception, and the major release for 2020 is great major, indeed.
Ruby 3 is causing lots of excitement throughout the Ruby universe and filling the heart of every Rubyist with joy and eager anticipation. Here at Vonage, we are big fans of Ruby. We have an entire suite of open-source tooling that we built on Ruby including an OpenAPI specification renderer, a markdown renderer, and a developer platform tool. All of these tools are above and beyond our Vonage APIs Ruby SDK and our Vonage Video API Ruby SDK.
In short, we love Ruby and we are bursting with glee for the upcoming release of Ruby 3.
What makes us so excited? While I cannot speak for the other Rubyists on the Vonage Developer Relations team (we have several Ruby fans on the team!) I can speak for myself. Some of the reasons why I am so enthused about December 25th this year are:
- Ruby Signatures (RBS)
- Ruby Actors (Ractors)
- More Pattern Matching
Let's dive into each one of these.
Ruby Signatures (RBS)
We have been exploring the benefits of adding static type checking to our Vonage Ruby projects for many months now. It was back in February of this year that we released version 6.3.0 of the Vonage APIs Ruby SDK that incorporated Sorbet into the library for the first time. We described in our blog post back then what our rationale was for doing so:
The introduction of static type checking in the Ruby SDK works to increase confidence in the API calls you are making with the SDK, and helps to both decrease and identify bugs as they occur.
In other words, we believed then and still believe now that with a combination of clear exception handling, concise documentation, and defined types for the methods that wrap the API calls, we can reduce the friction in using the SDK to build communications apps and services. The more complex the feature you are working to implement often corresponds to more complex parameters in your API call, and we want the SDK itself to guide you in how to craft that call successfully without banging your head against the wall... too many times.
Ruby 3 takes type checking to a whole new level by incorporating it right in the language itself. Whereas Sorbet required adopting an external gem into the application, Ruby will now natively support types. This is going to be accomplished with Ruby Signatures or RBS for short. For example, this is what a method from our Ruby SDK might look like using RBS:
def unicode?: (String, text) -> bool
!Vonage::GSM7.encoded?(text)
end
This small method from our SMS class checks for a true
or false
value on a String
parameter. Thus, with RBS, the parameter is typed with its definition of String
, and the method is typed to return a bool
, representing a Boolean value.
The ability to incrementally add type checking to all of our Ruby projects using the language itself and not any other external dependencies will allow us to achieve even more performance and stability benefits for our applications.
Ruby Actors (Ractors)
Parallel thread-safe programming in Ruby? Yes, it's true! Welcome to the wonderful new experimental world of the Ruby Actor or Ractor for short. Ractors allow for concurrent execution inside your Ruby application. This is a really big step for the Ruby language in general, and one with lots of potential use cases. The example provided in the Ruby 3.0 Preview 1 Release Notes encapsulates it well:
require 'prime'
# n.prime? with sent integers in r1, r2 run in parallel
r1, r2 = *(1..2).map do
Ractor.new do
n = Ractor.receive
n.prime?
end
end
# send parameters
r1.send 2**61 - 1
r2.send 2**61 + 15
# wait for the results of expr1, expr2
puts r1.take #=> true
puts r2.take #=> true
In the above example, we are determining whether a given calculated integer is a prime number. The Ractor is initialized with a #new
block and inside the block, we call #receive
on the Ractor class. The #receive
method, along with its complementary #send
method, is how messages are passed in Ractors. After invoking #receive
, the final task in the #new
block is to call #prime?
on the number being received by the Ractor.
The number is sent for evaluation to the Ractor, in this example, there are two Ractors initialized, with the #send
method and passing the object that is to be sent.
The value is extracted from the Ractor with the #take
method, which is what happens on the last two lines of the above example.
Previously, this would have not been able to happen in Ruby concurrently, but now within the context of the Ruby Actor, both r1
and r2
can happen as two non-blocking thread-safe executions.
More Pattern Matching
It was back in the Ruby 2.7 release that we were finally granted pattern matching in Ruby. For developers who have had experience with other languages that leverage pattern matching to a great extent, like Elixir for example, this was a big deal. What is pattern matching in brief? A blog post by Agnieszka Malszkiewicz explains it succinctly:
Pattern matching is a way to specify a pattern for our data and if data are matched to the pattern we can deconstruct them according to this pattern.
Angnieszka goes on to expand in great detail on how to take advantage of pattern matching in Ruby since version 2.7 with lots and lots of examples. I highly recommend exploring her post.
The skeleton of pattern matching in Ruby looks like this:
case expression
in pattern
do something
in pattern
do something else
else
otherwise do this
end
With Ruby 3 we have even more functionality added to pattern matching with the introduction of the find pattern in Ruby. To see how it works let's create a small example.
Perhaps we have an object that has an array of multiple elements inside of it and we want to pattern match against it:
today = {weather: 'Sunny', drinks: [{name: 'Espresso', daily_frequency: 3}, {name: 'Cold Brew', daily_frequency: 2}, day: 'Tuesday']}
If we tried to pattern match for something inside the drinks
array we would return a NoMatchingPatternError
. Now, utilizing the find pattern we can match against items with multiple elements:
case today
in {weather: 'Sunny', drinks: [*, {name: 'Cold Brew', daily_frequency: frequency}, *], *}
puts "#{frequency} times a day"
end
# => 2 times a day
Pattern matching, Ractors, and integrated type checking are just some of the amazing new features and improvements coming to Ruby this holiday season. What are you excited for? What have you been looking forward to experimenting and building with? Come join the conversation on our Vonage Community Slack or send me a message on Twitter.
If you're curious to explore our work with Ruby come and check out our open-source tooling built in Ruby on GitHub:
- Station
- Nexmo Markdown Renderer
- Nexmo OAS Renderer
- Vonage APIs Ruby SDK
- Vonage Rails Gem
- Vonage Rack Middleware
- Vonage JWT Generator for Ruby
Happy coding and happy holidays!
Top comments (3)
Hey guys, great article, just one thing that made me wonder:
I'm pretty sure you don't need the
*
in front of the sequence, becausemap
returns an array. So this works as well:The
*
is required when we want to splat sequences:Hey Thorsten, thanks for commenting! The code snippet for the Ractor section is mentioned as coming from the Ruby release notes not from me. :) Instead of reinventing the wheel I just shared theirs. The link to the original is in the post, too: ruby-lang.org/en/news/2020/09/25/r...
In the Ractor section,
Ractor.receive
is nowRactor.recv
:)