Variable Pinning
This post contains a two-part tip of the week from the April 15, 2021 and April 22, 2021 Ruby Weekly Newsletters.
Part One
Ruby 2.7 introduced pattern matching as an experimental feature. As of Ruby 3.0, only small parts of pattern matching are still experimental, which means we can learn about the stable parts.
Variable pinning is a new feature with pattern matching which we’ll discuss in two consecutive tips. It’s first necessary for us to understand the goals of pattern matching. From the docs, “Pattern matching is a feature allowing deep matching of structured values: checking the structure and binding the matched parts to local variables.”
Pattern matching uses the case/in/else
expression, similar to the case/when/else
expression. It allows us to match patterns, not just exact values:
case [1,2]
in [1,a]
"pattern match: a: #{a}"
in [1,2]
"second case"
end
=> "pattern match: a: 2"
Woah! We can see that it matched the [1,a]
case before the [1,2]
case because our array matched the pattern [1,a]
. And what’s more: the value of a
was then set to 2
, the second element of our array in the case statement.
You may be wondering what would happen if we wanted to try pattern match against an existing local variable. Say we had already defined a
before the case statement:
a = 3
case [1,2]
in [1,a]
"pattern match: a: #{a}"
in [1,2]
"second case"
end
=> "pattern match: a: 2"
Uh-oh. What’s going on? It’s telling us a
is 2
and seems to be overriding our local variable a
, which has a value of 3
. This is where pinning comes in! If we wanted to use the local variable a
, we would use the pin operator, ^
to signify that we were referrring to our local variable:
a = 3
case [1,2]
# We'll now use ^a
in [1,^a]
"pattern match: a: #{a}"
in [1,2]
"second case"
end
=> "second case"
And we can see that with the pin operator, we no longer match the first case, which would be [1,3]
. Just to confirm we could match the first case if the second element in our array was 3
, let’s change our array to be [1,3]
:
a = 3
case [1,3]
in [1,^a]
"pattern match: a: #{a}"
in [1,2]
"second case"
end
=> "pattern match: a: 3"
Check back in next week for another use case of variable pinning!
Part two
In the Tip of the Week last week, we discussed how we could use variable pinning in pattern matching to compare a pattern to a local variable. Another use case for variable pinning in pattern matching is to compare a value within the same pattern.
Let’s say we want to make sure that both elements in an array are the same, but we don’t care exactly what they are. We can use variable pinning again here. Let’s take a look:
case [1,1]
in [element,^element]
"elements are the same: #{element}"
else
"elements are different: #{element}"
end
=> elements are the same: 1
We assigned the first element to the variable element
, and then used variable pinning (^element
) to compare the second element to the first element.
We can also confirm that if the two elements in the array weren’t the same, we’d fall into the else
case, with element
sitll set to be the first element of the array:
case [1,"different"]
in [element,^element]
"elements are the same: #{element}"
else
"elements are different: #{element}"
end
=> "elements are different: 1"
The Ruby docs give an interesting and practical use case for variable pinning. In the case from their example, we have a nested data structure where we have the school that jane
is in, and the ids corresponding to each type of school. We want to find the id
that matches her specific schoool type:
jane = {school: 'high', schools:[
{id: 1, level: 'middle'},
{id: 2, level: 'high'}]
}
case jane
# select the last school, level should match
in school:, schools: [*, {id:, level: ^school}]
"matched. school: #{id}"
else
"not matched"
end
#=> "matched. school: 2"
Neat! We assigned the variable school
to the value with the key school
, and then pinned school
to ensure it matched the level. This led us to the id
we sought for high
school, 2
. Variable pinning in pattern matching can be very helpful in these cases, as it allows us to match to values within the pattern itself.