User Class Hierarchies with RubyWrite
October 2, 2009I see problems with each of the two approaches that we have tried with RubyWrite. If we create a sub-class on the fly (as we were doing earlier) then the problem is that we don’t have a handle on the class name and we cannot create multiple instances, as seems desirable in some cases. If we rename methods within the same class (as we are doing currently) there is the hack-ish issue of ensuring that we don’t redefine methods and don’t step over user-defined methods, aside from making a redundant check each time a new instance is created. Both leave something to be desired.
How about using a specially defined method to create a new class?
klass = RubyWrite::define do
def main
...
end
...
end
xformer = klass.new
Alternatively,
RubyWrite::define :Klass do
def main
...
end
...
end
xformer = Klass.new
We can now create an internal class and subclass it. Either the subclass is returned (first option) or given the name Klass (second option).
However, all of the above approaches still have one problem: due to the requirement that the user must create a new class by sub-classing RubyWrite::ReWriter, creating a hierarchy of user classes is not easy. If I wanted to modularize the design of my compiler I couldn’t create a “base” transformer and then subclass it to create other transformers. This might be important for implementing related compiler passes, for example, different data-flow analyses.
Implementing RubyWrite as a module is the obvious solution (already, much of it is in modules).
One possible approach with modules is to require users to use a factory method to create new objects, since we cannot redefine new. The factory method will use singleton methods to wrap user-defined methods. Class hierarchy won’t matter, although we WILL need a way to identify user-defined methods in the hierarchy. Even with instantiation overheads, this might be a workable solution, because I can’t imagine a scenario where lots of instances of a rewriter would be needed. If a user instance maintains state, the user is aware of that and can work around if absolutely necessary.
Another possibility is to sidestep the whole issue of monkey-patching by requiring that transforming “methods” be defined using special syntax. E.g.,
class MyTransformer
rewriter expRewriter do |node|
...
end
rewriter stmtRewriter do |node|
...
n = rewrite someNode, expRewriter
end
end
This has the downside of regular methods being different from rewriters (may not be so bad) and the somewhat awkward syntax for invoking rewriters (may be fixable with some creativity).
A hybrid approach is also possible, wherein any “regular” method may be used as a rewriter, but must be called with a special syntax to be used as a rewriter. I do see a couple of advantages of this hybrid approach—users can freely mix rewrite methods with other helper methods, but will always be aware when a method is a rewriter due to the special syntax. Also, this interferes minimally with Ruby class hierarchies and semantics, thus freeing user classes and objects to, for example, define their own singleton classes, if they so desire.
Posted by Arun Chauhan