TITLE: Beautiful Hacks Live On AUTHOR: Eugene Wallingford DATE: November 05, 2005 2:14 PM DESC: ----- BODY: As PragDave recently wrote, the Ruby Extensions Project contains a "absolutely wonderful hack". This hack brought back memories and also reminded me of a similar but more ambitious project going on in the Ruby world. First of all, here's the hack... One of Ruby's nice features is its collections' internal iterators. Instead of writing a for-loop to process every item in a collection, you send the collection a map message with a one-argument block as its argument. PragDave's example is to convert all the strings in an array uppercase, and the standard Ruby code is

names.map { |name| name.upcase } The map method applies the block -- effectively a nameless method -- to every item in names. This is a very nice feature, but after you program in Ruby you want more... Isn't writing that block more work than I should have to do? Why can't I just pass upcase, which is a one-argument method, as the argument to map? Because upcase isn't a method or a procedure; it's a symbol that names a method. As PragDave reports, the Ruby Extensions hack adds a method to the Symbol class that allows Symbols to be coerced to a procedure object at run-time. The result is that we can now write the following:

names.map(&:upcase) That's more succinct and avoids the need to create a new procedure object on the fly. Now the memory... I distinctly remember the afternoon I learned the same hack in Smalltalk, back in the summer of 1988. You see, Smalltalk's collections also offer an array of internal iterators that eliminates the need to write most for-loops, and these iterators take blocks as arguments. I was just learning Smalltalk and loved this idea -- it was my first exposure to object-oriented programming and the power of blocks, which are like functional programming's higher-order procedures. I was working on some code to sort a collection. In Smalltalk, collections also respond to a sort: message, whose argument is a two-argument block for comparing the values. So, to sort a collection of strings in ascending order, I could write:

names sort: [ x y | x < y ] But soon I wanted more... Why can't I just pass < to sort:? The same reason I can't do it in Ruby: < isn't a block or a method; it is a symbol that names a method. While figuring this out, I learned how sort: works. It turns around and sends the block a value:value: message with pairs of values from the collection as the two arguments. Hmm, what if a Symbol could respond to value:value: and do the right thing? I added a one-line method to the Symbol class, and they could:

value: anObject value: otherObject
      ^anObject perform: self with: otherObject
(perform: behaves something like Scheme's eval procedure.) And now I could write the wonderfully compact

names sort: < I soon added the one-argument form to Symbol, too, and began to write new code with abandon. I felt pretty smug at the time, for having created something so beautiful. But I soon learned that I had merely rediscovered a hack that every Smalltalk programmer either reinvents or sees in someone else's code. That didn't diminish the beauty of my idea, but it did extinguish my smugness. It turns out that this new hack is not only more compact and more convenient to write, but it runs faster than the block alternative, too. The callback on the symbol doesn't require the creation of a block on the fly, which requires memory allocation and the whole bit. Finally, for the more ambitious project... This cool hack points out that Ruby and Smalltalk method names are symbols, and without some coercion they are not evaluated to their method values in ordinary use. What if messages were first-order objects in these languages? If so, then I can writing message sends that take other messages as arguments, thus creating higher-order messages. If you are interested in this idea, check out Nat Pryce's recent efforts to add higher-order messaging to Ruby, if you haven't already seen it. Nat gives a link to the paper that laid the foundation for this idea and steps you through the value of higher-order messages and their implementation in Ruby. It's another absolutely wonderful hack. -----