gypsydave5

The blog of David Wickes, software developer

Destructuring in a Method Block

I went from genius to idiot - very rapidly - when looking at Enumerable#map and #inject in Ruby Monk, a free resource to help learn ruby that I’ve found really useful. Let’s start with ‘genius’ (although not really genius, more wrong but lucky).

The question was as follows:

Exploit the fact that map always returns an array: write a method hash_keys that accepts a hash and maps over it to return all the keys in a linear Array.

Exciting, right?

The solution that RM gave was this:

def hash_keys(hash)
    hash.map { |pair| pair.first }
end

But I went for:

def hash_keys(hash)
    hash.map {|key,value| key}
end

Not much difference, but enough. I had become confused when I entered my code, thinking more about the each_with_index method mentioned in the same page above. I was forgetting that #map would be sending back a single value, an array of the key-value pair. Ruby Monk’s solution used that fact to use the method #first on the array, to get the first element (the key) out.

But, by chance, my solution worked - it split the array by telling the block for map that it would be getting two arguments - checking with the good people on the #ruby channel on IRC (a chap called bannisterfiend to be precise), this is destructuring, binding a set of values to a corresponding set of values that you can normally bind to a single variable (that’s the definition as given in Common Lisp, but makes sense here).

What we’re seeing is this:

x, y = [1,2]
x # => 1
y # => 2

But done as a loop for all the arrays pulled out of the hash by #map. Tony Pitluga sings the praises of destructuring block arguments (which is what this is) in his longer article on destructuring in Ruby.

There’s an improvement in the semantics of the method (would you rather have key or pair.first?) and a saving of writing/time in any later work you might want to do on the values. Of course, we wouldn’t want to do this to longer arrays - too many elements to bind to values.

Unless we used the ‘unarray’ or, ‘star’ or (best name evar) splat operator to collect some of the other elements.

Consider:

x, *y = [1,2,3,4,5,6j
x # => 1
y # => [2,3,4,5,6]

There’s a lot more on the magic of the splat in the second edition of The Well Rounded Rubyist which has just been published (and which I’m enjoying immensely).

OK, I’ll write up my other mistake (the one that broke the #map block) later in the week.

PS - noticed when getting links for this post that Ruby 2.0 introduces the double-splat operator for turning keyword-value pairs in argument lists into a hash. Which is also exciting.