This post originated from an RSS feed registered with Ruby Buzz
by Michael Granger.
Original Post: Why I Love Ruby, Episode 12
Feed Title: devEiate
Feed URL: http://deveiate.org/feed/rss/blog
Feed Description: A blog about Ruby, codecraft, testing, linguistics, and stuff. Mostly stuff.
I’ve been doing quite a lot of thinking in Java for work lately, and I’m constantly amazed at how much effort it take to express anything useful above the class level of abstraction. The reflection API is a torture device. There’s so much time spend coddling the compiler, making it understand what you’re trying to do, that it’s often quicker to just stop trying to express things at a higher level of abstraction and just do everything by hand. Both of the Java IDEs I’ve used have code-generation facilities to make this less painful, but dependency on an IDE is a symptom of language deficiency, and generated boilerplate source reduces the expressiveness of the code. It’s the difference between an Army manual and a Mark Helprin novel.
Anyway, I’ve been working on both the FaerieMUD natural language generation code and the OOParser lately after work, and I’ve found a few new tricks for doing delegation that make me remember why I love Ruby.
The first is a way of optimizing delegation.
The OOParser class defines a domain-specific grammar language to express the rules for a top-down parser. In order to support the goal of making truly object-oriented parsers, rules must be able to be overridden by subclasses of the parent parser class, and I wanted to be able to use other tricks like #method_missing and aliases with rules as well, so they had to be implemented as methods. So the rules become Rule objects, and each Rule object’s #match method is delegated to by the correspondingly-named method of the parser object which contains them.
For example:
class IntegerParser<OOParsergrammar{default:integerdef_rule:integer,"<decimal>"do|match,state|state.log.debug"Match is: %p"%[match]returnInteger(match[:decimal])enddef_rule:decimal,/\d+/}end
This defines a parser with two rules: integer and decimal. These are instance methods of the parser objects, and can be used as methods to enter the parse at any point:
Not a very glamourous example, but I hope you can see how that would be useful when building parsers for more complex targets.
Anyway, on to the delegation bit. Usually, delegation happens as a kind of handoff from the receiving object to the delegated one, e.g.,
def foo(*args)@delegatee.foo(*args)end
This means that every call to #foo becomes two method calls, which isn’t normally a problem. In the case of a recursive-decent parser, however, where rules may be called many hundreds of times in the course of a typical parse, it can be. So OOParser uses Ruby’s superlative reflection to install the delegated object’s method directly into the containing object, making the call collapse back into one invocation:
# A bit simplified for demonstrationklass=parser_subclassgrammar.rules.eachdo|name,rule|block=rule.method(:match).to_procklass.__send__(:define_method,name,block)end
I’ve run out of time to post the second example, but I’ll continue this in a later post.