# Ruby 2.7 — Pattern Matching — Destructuring on Point

Apr 18 · 5 min read

Now that pattern matching has hit Ruby Nightly as an experimental feature, let’s take a look into some potential usecases for it starting with Destructuring.

If you haven’t seen the first article going over a lot of the spec, you can find it here:

# Testing Warning!

If something doesn’t match, it’s going to raise an error, so be sure to use `else` to handle default cases.

# On Point!

`Point = Struct.new(:x, :y) do  def deconstruct    self.to_a  end    def deconstruct_keys(keys)    self.to_h  endend`

We’ll use this as our base example for now.

# Array Destructuring

`x, y = Point.new(0, 1).to_ax # => 0y # => 1*coords = Point.new(2, 3).to_acoords # => [2, 3] `

These are all valid in `in` expressions in a pattern matching context. That includes splatting values.

Direct Value

We can destructure to match directly against values:

`case Point.new(0, 1)in 0, 1 then Point.new(0, 2)end=> #<struct Point x=0, y=2>`

These items all respond to `===` as we’ll see later in this article.

Triple Equals Destructuring

Anything that responds to `===` is perfectly fair game here:

`case Point.new(0, 1)in 0.., 1.. then Point.new(0, 2)end`

Direct Variable

If we wanted to just move north, we can use pattern matching to pull out our x and y values by position:

`case Point.new(0, 1)in x, y then Point.new(x, y + 1)end=> #<struct Point x=0, y=2>`

So it looks like the `then` keyword is still valid here. Good to know.

Now something interesting is also happening here. It’s assigning local variables, meaning after that statement this works:

`[x, y]=> [0, 1]`

This works with any of the variable assignment styles, and caught me a bit by surprise though it does make sense.

Triple Equals Destructuring into Variables

How about if we have some ranges?

`case Point.new(0, 1)in 0..5 => x, 0..5 => y  Point.new(x, y + 1)end#<struct Point x=0, y=2>`

What’s important to note here is that the format is:

`value or matcher => destructured variable`

These will respond to anything implementing `===` , which is what makes case statements so powerful in Ruby. Read this for more information on `===`:

# Keyword Destructuring

What we don’t necessarily get in Ruby is the ability to natively destructure on keywords, but with pattern matching we can if and only if `deconstruct_keys` is defined and returns a hash-like object like above:

`def deconstruct_keys(keys)  self.to_hend`

I’m not sure what keys are doing here, I’ll have to take a TracePoint at this to try and find out what’s going on later. If you have ideas let me know!

Considering Structs kind-of already do some of this, that’s an interesting technicality but not one we’ll worry about for now.

Keywords are not Variable Assignments

The thing to be careful of here is that the keys are used for destructuring, but not assignment:

`case Point.new(0, 1)in x: 0, y: 1..5 then Point.new(x, y + 1)endNameError: undefined local variable or method `x' for main:Object`

Arrows are still used for Assignment

So that doesn’t work. We have to use `=>` here to bind them to a local variable:

`case Point.new(0, 1)in x: 0 => x, y: 1..5 => y then Point.new(x, y + 1)end=> #<struct Point x=0, y=2>`

This means we get full access to `===` here as well, which can be very useful.

# Emulating Qo — Preview

This is a preview of some of the next article.

`Person = Struct.new(:name, :age) do  def deconstruct    self.to_a  end    def deconstruct_keys(keys)    self.to_h  endend`

We’ll also be using the `Any` gem for a wildcard:

Name is Longer than 3 Characters

The Qo way:

`name_longer_than_three = -> person { person.name.size > 3 }people_with_truncated_names = people.map(&Qo.match { |m|  m.when(name_longer_than_three) { |person|    Person.new(person.name[0..2], person.age)  }  m.else})`

The Pattern Matching way:

`person = Person.new('Edward', 20)case personin name: -> n { n.size > 3 } => name, age: Any => age  Person.new(name[0..1], age)else  personend=> #<struct Person name="Ed", age=20>`

# Wrap Up

There are also some very odd things like `keys` I don’t understand, and what happens if you try and do anything fancy in a pattern match like this:

`case Point.new(0, 1)in x: :even?.to_proc => x then Point.new(0, 0)endendSyntaxError: unexpected '.', expecting `then' or ';' or '\n'  in x: :even?.to_proc => x then Point.new(0...`

I believe this is likely a bug in the parser, but as this is experimental that’s to be expected.

My next few dives into pattern matching will likely follow trying to emulate various features I’d used Qo for:

2.7 is shaping up to be a very interesting release. Let’s see where it goes from here!

Written by