Your Web News in One Place

Help Webnuz

Referal links:

Sign up for GreenGeeks web hosting
March 1, 2020 01:58 am GMT

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

Share this article:    Share on Facebook
View Full Article

Dev To

An online community for sharing and discovering great ideas, having debates, and making friends

More About this Source Visit Dev To