Just A Summary

Piers Cawley Practices Punditry

Ruby 'til 6

Oh, I say. It seems that Sam Ruby is another member of the “Ruby ’til [Perl] 6” club.

I like Ruby a lot. For the kind dynamic OO/Functional coding style that I espouse, it’s a better Perl than Perl simply because it’s so much less verbose (I got so tired of always unpacking the argument list, it tended to put me off applying the Composed Method pattern anywhere near often enough).

But it’s not my One True Language. Perl 6 looks like it might be an awful lot closer to it. If nothing else, it has Lisp style macros.

A good macro system, especially when it’s combined with an accessible and well abstracted runtime is an awfully useful thing. For instance, consider the Rails controller. In a rails controller, public methods are ‘visible’ as actions that can be accessed via the web (usually with a url of the form /:controller/:action). Protected and private methods aren’t accessible in the same way.

But sometimes it’s quite handy to have a method on the controller that shouldn’t be deemed to be an action, but which you might want to call from a model. The canonical example here is when you’re doing Double Dispatch. Here’s an example of bad code:

results = @search_results.collect do |item|
case item
when Comment: extract_comment_metadata(item)
when Trackback: extract_trackback_metadata(item)
else
fail “Oops!”
end
end

Look, we’re using a case statement that dispatches on the class of another object! This is a job for Polymorphism. Let’s assume that the two extract_*_metadata methods need to do some of the things that only a controller can and can’t simply be replaced with extract_metadata on the Comment and Trackback objects. Here’s how I’d rejig the controller code:

results = @search_results.collect {|item| item.extract_metadata_for(self) }

And the support code in the model classes looks like1

def Comment::extract_metadata_for(controller)
controller.extract_comment_metadata(self)
end

def Trackback::extract_metadata_for(controller)
controller.extract_trackback_metadata(self)
end

And look! I’ve saved a line of code! Except, it doesn’t quite work like that. The extract_*_metadata methods aren’t public, we’re not supposed to call them from the model, so we’ll have to work around it:

def Comment::extract_metadata_for(controller)
controller.send :extract_comment_metadata, self
end

def Trackback::extract_metadata_for(controller)
controller.send :extract_trackback_metadata, self
end

which isn’t the end of the world, but it does obfuscate an idiom that’s already unfamiliar to many readers. Not good.

If you were writing Rails in Smalltalk, you would just file your controllers web visible actions in an ‘actions’ protocol and have done with it. If you were writing it in Perl, you could use method properties, declaring actions with something like:

sub read :action { … }

Sadly, ruby doesn’t have method properties. What I’d like to be able to write is something like:

action read

end

And have that define my action and do the housekeeping so that the rest of the framework knows that it can dispatch to this from a web request. About the nearest you could get to this with current Ruby is:

action :read do

end

It’s not awful, but it’s not pretty either.

A good macro system would make it let you write things that feel even more like part of the language than the many excellent things that Ruby on Rails already does in this area.

Another example: I would dearly love to be able to ‘unwind protect’ my controller’s filter methods, it’d be great if I could write:

pre_filter whatever
allocate_a_bunch_of_resources
true
unwind_protect
tidy_up_allocated_resources
end

The idea being that, if a filter block returns false, or throws an exception, we walk back up the stack of filters that have been called and execute their unwind_protect blocks. Admittedly, this sort of thing wouldn’t be terribly easy to write with a macro system, but I’m not sure it’s even possible to write without one because you need to preserve the value from just before the unwind_protect to return to the filter’s caller. You’d have to move your unwind_protect earlier in the code:

pre_filter :whatever

def whatever
unwind_protect do
tidy_up_allocated_resources
end
allocate_a_bunch_of_resources
true
end

But that feels arse about face, and I don’t know how you’re going to catch any exception thrown by allocate_a_bunch_of_resources. I suppose you could do:

pre_filter :whatever

def whatever
unwind_protect do
tidy_up_allocated_resources
end
allocate_a_bunch_of_resources
true
rescue Exception => e
tidy_up_allocated_resources
raise e
end

But by now we’re looking at code that only a mother could love, and even then she’d need a pretty high tolerance threshold.

Macro systems let you play these sorts of games, they’re just another tool for molding the language into something that makes it easy to solve the sorts of problems you face from day to day. One of the great things about using Ruby on Rails is that DHH and his cohorts have done a fabulous job of language design. Rails contains a host of handy domain specific languages (the most obvious being the stuff for declaring object relationships in ActiveRecord, but there’s some very handy stuff in the tag building and RJS support libraries as well) that work well together and allow the programmer to concentrate on solving his particular problem. It’s just that on occasion, it would be nice if they could go that one step further.

Perl 6 will let me take those sorts of steps.

I’d just like to say to any schemers/lispers who’ve made it this far: “Yes, I know.”

1 It doesn’t quite, I’ve taken liberties with the def syntax to preserve some vertical space…

Published on Thu, 03 Aug 2006 02:34:00 GMT by Piers Cawley under , , .

If you liked this article you can add me to Twitter

Comment Ruby 'til 6

Powered by Publify – Thème Frédéric de Villamil | Photo Glenn