2 min read

Rightward Assignment in Ruby

Rightward assignment for deconstruction
Rightward Assignment in Ruby
Photo by Nick Fewings / Unsplash

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)