Talks

Pattern Matching In Ruby 2.7

Pattern Matching is an upcoming Ruby 2.7 feature. As an elixir enthusiast and a professional Rubyist, I installed Ruby 2.7 and tried out the feature. I would like to show to pattern matching is implemented in Ruby, it’s potential and what could be improved.

RubyConf TH 2019

00:00:07.130 Hello, everyone. Welcome!
00:00:09.650 Today, I'll be discussing pattern matching. But before I get into it, let me introduce myself.
00:00:13.670 My name is Tae Noppakun Wongsrinoppakun, though it's quite a long name, so I usually go by Tae. I currently live in Tokyo, but I am originally from Bangkok.
00:00:20.000 I use Ruby on Rails and Vue.js on a daily basis. About two years ago, I discovered a functional language called Elixir, which had been highly praised in the Ruby community.
00:00:28.970 I checked it out and learned about its pattern matching feature. It's really neat! Pattern matching allows you to deconstruct complex data structures by binding values from those structures to variables.
00:00:41.929 This capability gives Elixir, which is a dynamically typed language, a form of method overloading that you don’t see in Ruby. In Java, for example, method differentiation can be achieved through argument types, but in Ruby—being a dynamically typed language—you can't do that. However, Elixir manages to implement it effectively through pattern matching.
00:01:07.490 Now, to show you an example, let’s look at pattern matching in Elixir. It resembles multiple assignment in Ruby. For instance, we can assign values by doing: `a = 'hello', b = 'world', c = 42`. Furthermore, it can deconstruct an array into head and tail, where head is the first element and tail represents the rest of the array.
00:01:34.760 But pattern matching goes beyond just that; it also checks for values. For example, instead of binding a variable to the last element, we can directly check its value using `42`. If we set it to a value that doesn’t match (like 88 instead of 42), it raises an error because the last value doesn't match.
00:02:03.380 Not only does it bind variables, but Elixir's pattern matching also allows you to check types within these structures. You can further extend this concept to maps, which are akin to hashes in Ruby. For example, if you define a map with a key called `animal` and bind it to a variable 'animal' with the value `dog`, Elixir will return the expected output.
00:02:43.459 Using pattern matching, you can define multiple functions for processing different maps based on their keys, calling different processing methods depending on those keys. This approach à allows clear differentiation between logic for varying data structures.
00:03:19.640 So, to recap, pattern matching explains explicitly how to parse data structures. It assigns variable values directly, which helps Elixir maintain method overloading. Think of pattern matching as similar to if-else statements, but with more potent variable assignments.
00:04:05.380 You might wonder why I'm discussing a functional programming feature. In Ruby 2.7, they are implementing pattern matching as well. Currently, the feature is experimental, and if you download the latest Ruby development version, you'll see that it warns you not to run the pattern matching code in production.
00:04:30.930 Now, let’s talk about the syntax. In Ruby, you would use a `case` statement for pattern matching. You provide an expression or variable and use `when` to supply patterns. You can also utilize `if` and `unless` clauses. If none match, you have an `else` block for defaults—much like a regular case statement.
00:05:11.640 Let's look at a practical example. Suppose we have an array of translations where each entry includes the original language, the translated language, and the translated text. We want to verify that the original language is Thai and the translated language is English before printing the translation. In standard Ruby, I would assign the values to different variables and then check them accordingly.
00:05:59.610 Using pattern matching, however, you can directly assign and verify these values in a more expressive way. It checks that the lengths of the arrays match and binds the variables accordingly.
00:06:44.400 We can apply the same logic to hashes instead of arrays. In this case, I would typically check each value in a hash. But with pattern matching, I can specify the structure I expect in my hash and what values I want to bind to.
00:07:46.650 With pattern matching, you can define expected keys, check their values, and bind them to variables in a concise way. If you wish, you can use syntactic sugar for easier representation, wherein instead of writing out variable names twice, you can use a colon notation.
00:08:49.680 This effectively simplifies how you interact with hashes and collections. There exist other tools within pattern matching like pin operators, which evaluate variables instead of rebinding them. For example, when getting translations, instead of reassigning original and translated languages, you can utilize a pin operator, denoted by a `@`, to simply evaluate these variables against incoming data.
00:09:47.760 We also have an ignore operator that you can use when you don't care about a particular value. Both match cases, even when the values differ, because you’ve indicated that particular value doesn’t matter.
00:10:11.850 Additionally, Ruby 2.7 introduces an assignment operator within pattern matching. You can check for specific keys in a translation hash while assigning variables. This neatly encapsulates the whole process.
00:10:55.200 Now, the special method `initialize` has been enhanced to work with pattern matching. You can define these methods in your classes to facilitate pattern matching for instance objects. When a pattern match occurs against an instance of a class with specified destructured keys, it calls those defined methods.
00:12:44.940 As we explore Ruby's evolving landscape, it’s essential to recognize both the potential and limitations of pattern matching. While I appreciate the effort to integrate features from Elixir and increase Ruby's expressiveness, I hope we can address certain drawbacks in the syntax. Coming from Elixir, the syntax in Ruby can feel less intuitive due to its reliance on switch statements.
00:13:14.710 I found it a bit cumbersome to use. The verbosity makes it challenging to implement multiple patterns efficiently, leading to unwieldy code. Performance-wise, I've noticed it takes roughly double the execution time compared to standard hash lookup. However, I recognize the work being done and how this could enhance Ruby applications in the long run.
00:14:50.240 Thank you for your attention!