In Progress
Unit 1, Lesson 1
In Progress

Parameter Destructuring

I don’t often get language envy when looking at non-Ruby programming languages. But lately I’ve been working in some JavaScript projects, and one feature I really like about modern JavaScript is how easy it is to “destructure” associative arrays into separate variables. In this episode, guest chef Brandon Weaver shows us how to emulate that kind of destructuring in Ruby. In the process, we’ll learn more about Ruby’s handy method introspection features. Enjoy!

Video transcript & code

Parameter Destructuring

Every now and then I like to go out and explore other languages, and one thing that really caught my attention was destructuring in Javascript.


function moveNorth ({ x, y }) {
  return { x, y: y + 1};
}

moveNorth({ x: 1, y : 1 });
// => { x: 1, y: 2 }

It works by pulling values out of an object by a key.

Now this got me thinking, can we get something like that in Ruby?

In JavaScript, functions are effectively how you write methods. One of the closest Ruby equivalents to this is a lambda function, like this one:


move_north = -> x, y {
  { x: x, y: y + 1 }
}

The only problem is that if we call them, we can't pass in a literal hash any more!


move_north.call(1, 1)
# => { x: 1, y: 2 }

We can return one, but that just won't do. No no no, we want destructuring! So what Ruby magic is there to grant our wish?

Ruby functions come with this handy little method called parameters, let's take a look at the documentation of that:


> ? Proc#parameters

From: proc.c (C Method):
Owner: Proc
Visibility: public
Signature: parameters()
Number of lines: 4

Returns the parameter information of this proc.

   prc = lambda{|x, y=42, *other|}
   prc.parameters  #=> [[:req, :x], [:opt, :y], [:rest, :other]]

Oh? Let's borrow their example idea with what we had earlier:


move_north = -> x, y {
  { x: x, y: y + 1 }
}

move_north.parameters
#=> [[:req, :x], [:req, :y]]

Are those the names of the function arguments there? Why yes they are dear audience. Are you getting ideas? Good good!

We can get the names of all the params like this by grabbing the last item of each argument array tuple:


argument_names = move_north.parameters.map(&:last)
# => [:x, :y]

Now say we had a hash like this:


current_position = { x: 1, y: 2 }

If we already have those names, we could do something like this:


values = argument_names.map { |arg_name| current_position[arg_name] }
# => [1, 2]

Now get ready, because we're about to have some fun. What if we called the function with those values?:


move_north.call(*values)
# => { x: 1, y: 3 }

This is starting to look a lot like destructuring.

What if we put it all together in a method named destructure?

We can start with accepting a hash and a lambda block function like current_position and move_north from above.

Then we can extract the argument names like we did before by getting the function params.

Next we extract the values from the hash using those argument names.

And finally we call our original function with the values we just extracted


def destructure(hash, &function)
  argument_names = function.parameters.map(&:last)
  values         = argument_names.map { |arg_name| hash[arg_name] }

  function.call(*values)
end

Shall we give it a whirl? Let's take a look at our current position.


current_position = { x: 1, y: 2 }

We'd call it like this, click enter, and...


move_north = -> x, y {
  { x: x, y: y + 1 }
}


destructure(current_position, &move_north)
# => { x: 1, y: 2 }

Now that's fun! Looks like we have a start towards some destructuring in Ruby!

But the fun thing about the way that we wrote this is that we can actually call destructure with a block function!

Let's say we called it with the values x of 2 and y of 3, and give it a block function taking the arguments x and y.

We'll add one to x and subtract one from y to get something that looks a lot like our previous result.


destructure({ x: 2, y: 3 }) { |x, y| { x: x + 1, y: y - 1 } }
# => {:x=>3, :y=>2}

Learning from other languages can be fun, and it can be even more fun to take those lessons and see how we might be able to apply them to Ruby.

Happy hacking!

Responses