Rightward Assignment in Ruby
The righward assignment operator as a way to deconstruct more complex objects.
To begin at the beginning
Leftwards, or normal, assignment looks like:
x = 1
# => 1
Rightward assignment flips that around:
1 => x
# nil
x
# => 1
The most obvious thing to note is the value flows from right to left, 1
is-assigned-to the variable x
. The second is that rightwardly assigning returns a nil
.
We can assign the value of complex statements:
true ? 'yes' : 'no' => result
p result
'yes'
if false
'oh yes'
else
'oh no'
end => result
p result
'oh no'
Plus I think it actually looks rather nice in pipelines:
[2,4,1,5,3]
.sort
.filter(&:even?)
.max => biggest_even
p biggest_even
4
Arrays
We've long been able to split arrays with leftward assignment:
left, *right = [1,2,3,4,5]
left
# => 1
right
# [2,3,4,5]
and we can even gobble from the left
*left, right = [1,2,3,4,5]
left
# => [1,2,3,4]
right
# => 5
We can do a similar thing going rightwards:
[1,2,3,4,5] => *left, right
left
# => [1,2,3,4]
right
# => [5]
...but the real oompf comes from the find pattern†. Have a look at this and notice the brackets on the right hand side:
[1,2,3,4,5] => [*left, 3, *right]
left
# => [1,2]
right
# => [4,5]
Here the right hand side acts as a template for the matching. It reads: gobble everything to the left of the litteral 3 and put it in left
and, after the literal 3, gobble everything to the right and put it in right
.
† the Find Pattern is still experimental as of 3.1 and I'm avoiding further discussion of it here.
Hashes
This is much more interesting. Let's work with this hash:
a_hash = {
attribute: 'a wonderful thing',
nested_hash: {
attribute: 'a nested attribute'
},
an_array: [1,2,3,4,5]
}
Let's say we wanted to pull this apart a bit:
a_hash => {attribute:}
attribute
# => 'a wonderful thing'
a_hash => {nested_hash:}
nested_hash
# => {:attribute=>"a nested attribute"}
# or let's grab both the attribute and the nested_hash at the same time
a_hash => {attribute:, nested_hash: }
In the first example above, we match on a key called attribute
and, as a syntactic shortcut, assign the value to a variable also called attribute
. We can override that and give the variables explict names (I think I prefer this right now):
a_hash => {attribute: top_level_attribute}
Let's dig a little deeper. Say I want the value of the key attribute
from the top level and the value of attribute
from the nested hash:
a_hash => {attribute: top_level_attribute, nested_hash: {attribute: nested_attribute } }
I can also do the array denaturing trick inside a hash:
a_hash => {an_array: [first, second, *rest]}
Wrapping up
This feels like an awful lot of syntactic sugar to add in one go. The real power of this is going to lie in the Find Pattern when it becomes stable.
Also, Here be Dragons. If you try and match on a key or value that doesn't exist, you're going to get a full fat exception being thrown:
a_hash => {hey: }
(irb):in `<main>': {:attribute=>"a wonderful thing", :nested_hash=>{:attribute=>"a nested attribute"}, :an_array=>[1, 2, 3, 4, 5], :an_array_of_hashes=>[{:attribute=>:a}, {:attribute=>:b}, {:attribute=>:c}, {:attribute=>:d}]}: key not found: :hey (NoMatchingPatternKeyError)
Member discussion