2006-08-18

Vote Kernel for Toplevel Object


As a follow up to my last post on the "pain that is main", I want to offer a potential improvement for Ruby 2.0. I approached the topic on ruby-talk this week and while Matz initially took some interest, he hasn't followed up since his last comment:


matz: Why? If it is really required it's fairly
easy to add toplevel methods like we did for #include.


But it isn't always easy. In order get my Taskable module to work, for instance, I had to make exceptions for the toplevel case, which is far from ideal and is fragile [Ed- in fact I'm still getting bugs that I haven't yet pinned down]. These subtile difficulties arise becuase main acts as a partial proxy for the Object class. Anyone who has created a proxy object before knows the subtile issues that can come into play. In this case, only the bare minimal interface is supported --essentially the method #include. Yet, even if we take matz' advice and add in all the missing proxy methods, we still won't be 100% out of the woods. The Object class and main are fundamentally two distinct objects --self is not the same, nor are their singleton classes, &c. In the vast majority of cases this will never present an issue, but the distinction can creep in. Here's an highlight of one way it can:


module Q
define_method :q do
base = self
define_method :r do
base == self.class
end
end
end

class C
extend Q
q
end

c = C.new
p c.r

# per matz' direction

def define_method( name, &act )
Object.class_eval {
define_method( name, &act )
private name
}
end

extend Q
q
p r

produces

true
false


So in effect Ruby is mildly schizophrenic. The false reading is because main != Object. So, you can't neccessarily create a DSL for Object to be used in main, and you can't neccessairly create a DSL for main to be used in any Object. Hence the devolution to DRYless code.

There is a potentially elegant solution however, and I'd really like to understand others insights into this (esspecially Matz' of course): Instead of main being a special proxy object, just let it be a self extended module.


module Main
extend self
# programs are written as if in here
end


This would provide all the facilities required of the toplevel without all the proxy troubles. Also, while I'm not so convinced of the merits of every toplevel method becoming a private method of all objects (and with Main that can be easily prevented), it has proven workable in practice so it's not a significant factor of consideration here. Main can simply be include in Object to achieve that effect.


class Object
include Main
end


But when we do that it becomes very clear what Main appears to be: Kernel. That strikes me as esspecially interesting. Then again, there may be good reasons to keep the Kernel as a separate module, in which case we'd just have a class hierachy:


Object.ancestors
=> [Object, Main, Kernel]


Nevertheless, it is clear the Kernel could just as well serve as the toplevel object, which, IMHO, makes this an elegant proposition to consider. Perhaps I'll start a campaign as November elections roll around: "Vote Kernel for Toplevel Object!" ;-)

2 comments:

Anonymous said...

Thanks so much for this wonderful article. I had been wondering for a while about main and had also surmised that it was nearby Kernel however your column really cleared up my confusion. Cheers, Rudi Cilibrasi

tea42 said...

Thanks. I must admit the "schizophrena" is much milder than I had first thought. I creted a new Facets module called main_as_module.rb that fills in all the missing proxy methods, and it works pretty well. Even so there are still subtle edge cases, adn I outline a case with an example I just added to this blog post.