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.