TITLE: Faking a Cool Programming Idiom in Ruby AUTHOR: Eugene Wallingford DATE: June 12, 2012 3:44 PM DESC: ----- BODY: Last week, James Hague blogged about a programming idiom you've never heard of: fetching multiple items from an array with a single operation.
Let's say the initial array is this:
     10 5 9 6 20 17 1
Fetching the values at indices 0, 1, 3, and 6, gives:
     10 5 6 1
You can do this directly in APL-style languages such as J and R. In J, for example, you use the { operator:
     0 1 3 6 { 10 5 9 6 20 17 1
Such an operator enables you to do some crazy things, like producing a sorted array by accessing it with a permuted set of indices. This:
     6 1 3 2 0 5 4 { 10 5 9 6 20 17 1
produces this:
     1 5 6 9 10 17 20
When I saw this, my first thought was, "Very cool!" It's been a long time since I programmed in APL, and if this is even possible in APL, I'd forgotten. One of my next thoughts was, "I bet I can fake that in Ruby...". I just need a way to pass multiple indices to the array, invoking a method that fetches one value at a time and returns them all. So I created an Array method named sub that takes the indices as an array.
     class Array
       def sub slots
         slots.map {|i| self[i] }
       end
     end
Now I can say:
     [10, 5, 9, 6, 20, 17, 1].sub([6, 1, 3, 2, 0, 5, 4])
and produce [1, 5, 6, 9, 10, 17, 20]. The J solution is a little cleaner, because my method requires extra syntax to create an array of indices. We can do better by using Ruby's splat operator, *. splat gathers up loose arguments into a single collection.
     class Array
       def sub(*slots)             # splat the parameter
         slots.map {|i| self[i] }
       end
     end
This definition allows us to send sub any number of integer arguments, all of which will be captured into the parameter slots. Now I can produce the sorted array by saying:
     [10, 5, 9, 6, 20, 17, 1].sub(6, 1, 3, 2, 0, 5, 4)
Of course, Ruby allows us to omit the parentheses when we send a message as long as the result is unambiguous. So we can go one step further:
     [10, 5, 9, 6, 20, 17, 1].sub 6, 1, 3, 2, 0, 5, 4
Not bad. We are pretty close to the APL-style solution now. Instead of {, we have .sub. And Ruby requires comma-separated argument lists, so we have to use commas when we invoke the method. These are syntactic limitations placed on us by Ruby. Still, with relative simple code we are able to fake Hague's idiom quite nicely. With a touch more complexity, we could write sub to allow either the unbundled indices or a single array containing all the indices. This would make the code fit nicely with other Ruby idioms that produce array values. If you have stronger Ruby-fu than I and can suggest a more elegant implementation, please share. I'd love to learn something new! -----