The latest and greatest feature coming to Ruby 2.7 is pattern matching. For those eager to try it out, Ruby 2.7.0-dev is available for installation, but keep in mind that it’s still under development and the development team welcomes your feedback.
This article aims to provide a comprehensive understanding of pattern matching and its application in Ruby.
Understanding Pattern Matching
Frequently employed in functional programming languages, pattern matching, as defined by Scala documentation, is essentially “a way to compare a value against a defined pattern. A successful match can also break down a value into its individual components.”
It’s important to note that pattern matching is not about strings but rather about the structure of data. My initial encounter with pattern matching was around two years ago while exploring Elixir. I was learning the ropes of Elixir and using it to tackle algorithms. Upon comparing my solutions with others, I discovered their use of pattern matching, which made their code remarkably concise and readable.
The elegance of pattern matching left a lasting impression on me. Here’s a glimpse of pattern matching in Elixir:
| |
This example might resemble multiple assignment in Ruby, but it goes a step further by verifying if the values align:
| |
In the above instances, the number 42 on the left isn’t a variable being assigned a value. It acts as a check to ensure the corresponding element at that specific index matches the one on the right side.
| |
Here, instead of assigning values, a MatchError is triggered because 88 doesn’t match 42.
This concept extends to maps (similar to hashes in Ruby):
| |
This example verifies if the value associated with the key name is Zote and assigns the value of the key title to the variable title.
Pattern matching proves particularly valuable when dealing with intricate data structures. It allows you to assign variables and check values or types all in a single line.
Moreover, it empowers dynamically typed languages like Elixir to implement method overloading:
| |
The execution of different methods is determined by the key within the hash argument.
This hopefully illustrates the potency of pattern matching. Various attempts have been made to integrate pattern matching into Ruby using gems like noaidi, qo, and egison-ruby.
Ruby 2.7 introduces its own implementation, drawing inspiration from these gems. Let’s delve into how it’s currently designed.
Ruby Pattern Matching Syntax
Pattern matching in Ruby is implemented using a case statement, but instead of the familiar when, the keyword in is used. It also supports the inclusion of if or unless statements:
| |
A case statement can process a variable or an expression, which is then matched against patterns provided in the in clause. Additionally, if or unless statements can be added after the pattern. Similar to a standard case statement, the equality check here employs ===, allowing you to match subsets and instances of classes. Let’s illustrate this with an example:
Matching Arrays
| |
In this example, the variable translation is compared against two patterns:
['th', orig_text, 'en', trans_text] and ['th', orig_text, 'ja', trans_text]. The code checks if the values within the pattern correspond to the values in the translation variable at their respective indices. If a match is found, the values from the translation variable are assigned to the variables in the pattern at their corresponding indices.

Matching Hashes
| |
Here, the translation variable represents a hash. It’s compared against another hash in the in clause. The case statement verifies if all keys in the pattern exist in the translation variable. It also ensures that the values associated with each key match. If successful, the values are assigned to the corresponding variables in the hash.

Matching Subsets
The equality check in pattern matching adheres to the logic of ===.
Multiple Patterns
- The
|symbol can be used to specify multiple patterns for a single block.
| |
In this instance, the translation variable is checked against both the {orig_lang: 'th', trans_lang: 'ja', orig_txt: orig_txt, trans_txt: trans_txt} hash and the ['th', orig_text, 'ja', trans_text] array.
This proves beneficial when dealing with slightly different data structures representing the same concept, and you want a single code block to handle both.
Arrow Assignment
The => symbol can be employed for assigning a matched value to a variable.
| |
This is particularly helpful when you need to verify values within a data structure while also binding them to a variable.
Pin Operator
The pin operator prevents the reassignment of variables.
| |
In this example, the variable a in the pattern is matched against 1, 2, and then 2 again. It gets assigned to 1, then 2, and then 2 again. This isn’t ideal if you’re aiming to confirm if all values within the array are identical.
| |
When the pin operator is used, it evaluates the variable rather than reassigning it. In this scenario, [1,2,2] doesn’t match [a,^a,^a] because in the first index, a is assigned to 1. In the second and third indices, a is evaluated as 1 but is compared against 2.
However, [a,b,^b] matches [1,2,2] because a is assigned 1 in the first index, b is assigned 2 in the second index, and then ^b, which is now 2, is matched against 2 in the third index, resulting in a successful match.
| |
Variables defined outside the case statement can also be utilized, as demonstrated in the example above.
Underscore (_) Operator
The underscore (_) serves as a placeholder to disregard values. Let’s look at a couple of examples:
| |
| |
In these examples, any value matching against _ passes. In the second case statement, the => operator captures the disregarded value as well.
Practical Applications of Pattern Matching in Ruby
Imagine working with the following JSON data:
| |
In your Ruby project, you intend to parse this data and display the name based on these criteria:
- If a username exists, return the username.
- If the nickname, first name, and last name exist, return the nickname followed by the first and last names.
- If the nickname is absent but the first and last names are present, return the first and last names.
- If none of the conditions are met, return “New User.”
Currently, you might write this in Ruby as follows:
| |
Now, let’s see how this translates using pattern matching:
| |
While syntax preferences can be subjective, the pattern matching approach appears more favorable. It allows us to explicitly define the expected hash structure instead of describing and checking its values. This enhances the readability and understanding of the expected data:
| |
Instead of:
| |
Deconstruct and Deconstruct_keys
Ruby 2.7 introduces two new methods: deconstruct and deconstruct_keys. When an instance of a class is matched against an array or hash, deconstruct or deconstruct_keys are invoked, respectively.
The results returned by these methods are then used for pattern matching. Here’s an illustration:
| |
This code snippet defines a class named Coordinate with x and y attributes. It also defines the methods deconstruct and deconstruct_keys.
| |
Here, an instance of Coordinate is created and pattern matched against an array. In this case, Coordinate#deconstruct is called, and the result is compared against the array [a,b] specified in the pattern.
| |
In this example, the same instance of Coordinate is matched against a hash. The result of Coordinate#deconstruct_keys is then used for comparison against the hash {x: x, y: y} in the pattern.
An Exciting Experimental Feature
Having first encountered pattern matching in Elixir, I initially anticipated that this feature might encompass method overloading and be implemented with a more concise syntax. However, it’s understandable considering Ruby wasn’t designed with pattern matching in mind.
Utilizing a case statement is a relatively lightweight approach to implementation and doesn’t impact existing code (except for the introduction of deconstruct and deconstruct_keys methods). Interestingly, this use of the case statement resembles Scala’s implementation of pattern matching.
In my opinion, pattern matching is a promising addition to Ruby. It has the potential to enhance code clarity and bring a touch of modernity to the language. I’m eager to witness how developers embrace and leverage this feature as it continues to evolve.