An Interest In:
Web News this Week
- April 14, 2024
- April 13, 2024
- April 12, 2024
- April 11, 2024
- April 10, 2024
- April 9, 2024
- April 8, 2024
Find your way in Ruby Pattern matching
Last December we got for Christmas Ruby 2.7. release and between its many interesting features and improvements, one in specific is pretty much a first for the ruby language.
This article is an overview of it, ideal for ruby beginners curious about Ruby new features.
Pattern Matching
The experimental feature Pattern matching is, in the words of its creator, 'a combination of case/when and multiple assignments' and, if you are coming from Javascript, it might look like a quite lean switch statement.
That means we now can combine deconstruct arrays and hashes, and with matching its items with specific patterns or classes.
Pattern matching can be especially powerful when working with complex objects, like parsed JSON, YAML files, or even HTTP routers.
That all sounds very cool and all, but first let's play a bit with the basics of the feature.
Value pattern
Let's get used to the basic syntax.
It starts with the keyword case
followed by the object or variable you will be matching.
case 67
Then we add the first matching case with the keyword in
in the left.
In this one, we want to test if 67 is between 0 and 100.
case 67 in 0..100
This is the only possibility we want to match for right now, so let's ask it to print something if it matches and close it with the end
keyword.
case 67 in 0..100 p 'it\'s a match!'end
It should output "It's a match"
since the condition we matched for was true.
What if it's not the case though?
Let's test it again but with a number in the case that will fail the matching.
case 167 in 0..100 p 'it\'s a match!'end=> NoMatchingPatternError (167)
When the matching fails it outputs a NoMatchingPatternError followed by the tested object inside a parenthesis.
Let's handle when we don't have a match with an else
.
case 167 in 0..100 p 'it\'s a match!' else p 'Number not in range :-('end=> "Number not in range :-("
That's way better than getting an error.
It's possible to add multiple matching conditions too.
case 80 in 0..10 p 'A small number' in 11..50 p 'An okay sized number' in 51..100 p 'A respectable sized number' else p 'Number not in range :-('end=> "A respectable sized number"
Variable pattern
What if we want to print the value that got matched itself?
That's when binding a variable to it comes in handy. Let's start simple.
case 100 in num p "num got its name bound to #{num}"end=> "num got its name bound to 100"
And now we can combine the variable binding with the matching.
case 100 in Integer => num p "Bind #{num} only if matched"end=> "Bind 100 only if matched"
In the example above we first make sure that 100 is an Integer, and if that's true we bind its value to the variable num and print the message.
Alternative pattern
Sometimes more than one condition might be good.
make_it = 'better'case make_it in 'harder' | 'better' | 'faster' | 'stronger' => like_this p "Do it #{like_this}"end=> "do it better"
As pattern
It's also possible to test regular expressions
bad_email = 'wrongmailATmail.com'case bad_email in /.+@.+\.\w/ => email p "Sending to #{email}" else p 'This is not an email'end=> "This is not an email"
good_email = '[email protected]'case good_email in /.+@.+\.\w/ => email p "Sending to #{email}"end=> "Sending to [email protected]"
With Arrays
The real fun with Pattern matching starts when you use it with objects though. It's possible to match an array all the ways in the example below.
case [0, 1, 2] in Array(0, 1, 2) => arr p arrendcase [0, 1, 2] in Object[0, 1, 2] => arr p arrendcase [0, 1, 2] in [0, 1, 2] => arr p arrend=> [0, 1, 2]
Be careful with combining syntax sugar and variable binding though.
case [0, 1, 2] in 0, 1, 2 => arr p arrend=> 2
By default, arrays match exactly. The code bellow should fail the matching.
arr = [0, 1, 2]case arr in [1, 2] => a p aend=> NoMatchingPatternError ([0, 1, 2])
If we don't care for the first item the array we can use _
. Now it should only test the second and last array element and ignore the value of the first one.
arr = [0, 1, 2]case arr in [_, 1, 2] => a p aend=> [0, 1, 2]
Or even better, we can match only a subset of the array by using *
. Now it will only try to match the last array element with the number 2.
arr = [0, 1, 2]case arr in [*, 2] => a p aend=> [0, 1, 2]
We can also bind variables to array elements.
user = [7271, ['Marcela', 'Pena', 26]]case user in [id, [first_name, last_name, age]] p "User #{id}, #{first_name} #{last_name}, is #{age} years old"end=> "User 7271, Marcela Pena, is 26 years old"
Let's ignore some of the values with _
, and use a guard clause to check if the user is of age.
user = [7271, ['Marcela', 'Pena', 26]]case user in [_, [first_name, _, age]] if age >= 18 p "#{first_name} is #{age} years old" else p "User is underage" endend=> "Marcela is 26 years old"
With hashes
my_hash = {a: 0, b: 1, c: 2}case my_hash in Hash(a: a, b: 1) => h p hend=> {:a=>0, :b=>1, :c=>2}case my_hash in Object[a: a, b: 1] => h p hend=> {:a=>0, :b=>1, :c=>2}case my_hash in {a: a, b: 1} => h p hend=> {:a=>0, :b=>1, :c=>2}# careful if you go no brackets thoughcase my_hash in a: a, b: b => h p hend=> 1
Hash patterns are more flexible than array matches, by default they always test for subsets.
my_hash = {a: 0, b: 1, c: 2}case my_hash in {b: 1} p restend=> [[0, 1, 2]]case {a: 0, b: 1, c: 2} in a:, b: p a p bend=> 0=> 1
It's possible to make exact matches with hashes though.
my_hash = {a: 0, b: 1, c: 2}case my_hash in {a: 0, **rest} if rest.empty? p aend=>NoMatchingPatternError ({:a=>0, :b=>1, :c=>2})other_hash = {a: 0}case other_hash in {a: 0, **rest} if rest.empty? p aend=> 0
And power it up with deconstruction
user_hash = {id: 189, name: {first_name: 'Alanis', last_name: 'Morris'}, nick_name: nil, pets: {cats: ['Flufbell', 'Lady Merlin'], dogs: []}}case user_hash in {id: nil => id, name: } # save user if it doesn't have an id and has a name p 'Save new user!' in {name:, nick_name: String => nick_name} p "#{nickname} is all set up" in {name: {first_name:, last_name:}, nick_name: String => nick_name, pets: {cats:, dogs:}} if cats.empty? && !dogs.empty? p "#{first_name} nick name will be #{last_name}-woof" in {name: {first_name:}, nick_name: nil => nick_name, pets: {cats:, dogs:}} if !cats.empty? && dogs.empty? p "#{first_name} nick name will be Catty#{first_name}" in {name: {first_name:}, nick_name: nil => nick_name, pets: {cats:, dogs:}} if !cats.empty? && !dogs.empty? p "#{first_name} nick name will be #{first_name}Meoof" else 'Sign up' end=> "Alanis nick name will be CattyAlanis"
Now that you got used to the basics of pattern matching and really want to go into the meat of it make sure to read the official documentation and other awesome in-depth articles written about it and to watch Kazuki Tsujimoto presenting it at RubyConf 2019.
Let me know if this article was useful for you or if it can be improved.
Happy codding!
Original Link: https://dev.to/uryelah/find-your-way-in-rubys-pattern-matching-1n43
Dev To
An online community for sharing and discovering great ideas, having debates, and making friendsMore About this Source Visit Dev To