Names, name, name... no more!
Quick! What annoys you most about Ruby?
Okay, okay. Even avid Rubyists can probably think of more than a few
answers to that questions. But here's one of my standard grumbles:
burgers.map{|burger| burger.sauce}
Why does this simple application of map annoy me? Well, I
had to write the word "burger" three times. The reasons this happens
is because in Ruby, verbs (methods) only exist as part of their nouns
(objects). So we need to name our intermediate in order to call a
method on it.
Lisp object systems often handle this in a different way and instead
use "generic functions." This puts the "method" code in tangible
function objects that can then be passed around. Of course these
generic functions must be smart enough to call the appropriate code
depending on the arguments they are called with (Ruby uses only the so
called "invocant", the object before the dot, to make this
decision).
The ability to choose different code based on the arguments is very
important for polymorphism. After all, we'd like both our "Sheep" and
"Goat" classes to have an "eat" method that do different things,
without having to call them "sheep-eat" and "goat-eat".
Of course, one frustration with many Lisp systems is that while they
have generic functions built in, the base system is not built with
them. Thus, built in objects either have erratically named companion
functions (to avoid namespace collisions) or sanely-prefixed, but
unweildy names (like Scheme hashtables from SRFI-69, which have the
"hash-table-ref" and "hash-table-set!" methods).
Anyways, If you're interested in this sort of thing in Ruby, have a
look at my Multiple Dispatch for
Ruby library or my article If
It's Not Nailed Down, Steal It: Pattern Matching, S-Expressions, and
Domain Specific Languages in Ruby. Multiple dispatch is not a very
good fit for Ruby, since Ruby already has its own object/method model,
but it can be fun to play with anyways.
Okay, so in Scheme using generic functions you would write:
(map sauce burgers)So here's the good news: this level of succintness is coming to Ruby! Matz just merged the following neat trick into Ruby 1.9, letting you write:
burgers.map(&:sauce)Wooo! Count it, "burger" is written only one time! So what's going on here? Well, ":sauce" is a symbol. The trick is that the Symbol class now has a "to_proc" method. Apparently, when a non-Proc object is passed into a method call in the Proc position, it is coerced into a proc using the "to_proc" method. Thus, calling "to_proc" on a symbol now yields a proc that invokes the method with the name of that symbol on the proc's argument. Pretty slick. You can get the code to do this right now at the Ruby Extension project while you wait for Ruby 1.9. However, if you're interested in playing around with "to_proc" in general, here's an example to get you started.
class Cow
def to_proc
return Proc.new{ puts "moo" }
end
end
3.times(&Cow.new)
Pretty cool, huh?
PS- The "&:" prefix is a little line noisy, but I don't feel too bad,
because Common Lisp doesn't nail this either. Because of Common Lisp's
separate function and data namespaces in order to pass the method in,
you need to write:
(mapcar #'sauce burgers)Schemers should feel free to gloat. Or is that (schemers-feel-free-to-gloat)? ;-)
posted on: 06/12/2006 | path: /tech