Charles Oliver Nutter
12/22/2007 2:51:00 PM
Stefan Rusterholz wrote:
>> Or perhaps, the various implementers will be able to answer this
>> properly as well :)
>>
>> So here's a long answer:
>>
>> Selector namespaces would be difficult to implement largely because of
>> how method dispatch works. Currently, the metaclass of the target object
>> is 100% in control. It decides which method will be called in all cases,
>> and the caller is not able to influence that selection process in any
>> way. So we have to monkey patch the actual metaclass to have new
>> behavior show up for all future calls, with the down side of course
>> being that new behavior shows up for all future calls...you can't choose
>> that some paths see new behavior and some see old.
>>
>> Groovy is an example of a modern language that supports selector
>> namespaces, which they call Categories. Categories allow you to apply a
>> set of metaclass changes to one (or more?) class on a specific thread
>> within a specific scope. So you can add behavior to String for a block
>> of code and all code it calls, and when the block finishes the behavior
>> disappears.
>>
>> And it's dog slow, even compared to normal non-Category Groovy calls
>> which aren't particularly fast to begin with.
>>
>> The way it's implemented in Groovy leverages the fact that all
>> invocations in Groovy go first to an intermediary that decides whether
>> the target object's metaclass should have the last say or not. If a
>> Category is in play, all such intermediaries will allow the Category to
>> have first grab at method invocation, at which time they can pretend the
>> target metaclass has the new behavior. Otherwise, calls pass straight
>> through to the metaclass.
>>
>> The problem with this is that when you're *not* using a Category, you
>> still have to constantly check for each invocation whether a Category
>> has been installed. Check, check, check, check, check, check...wasting
>> cycles. It also adds multiple additional layers into method invocation
>> when you *are* in a Category, since it stacks all the Category logic on
>> top of the already heavy method invocation logic.
>>
>> If not for Categories, no intermediary would be needed, and no checks
>> would be needed.
>>
>> I'd expect similar semantics in Ruby, since non-thread-local namespaces
>> would be almost worthless (threads would see behavior on types change
>> forward and back at arbitrary times), and as a result similar
>> performance implications. Even worse, it would require shoehorning an
>> intermediary into the Ruby call path, further slowing it down and
>> complicating optimization by VMs like the JVM and CLR. And it would add
>> in all the same checks, since every call would have to check whether a
>> namespace has been installed before proceeding.
>>
>> - Charlie
>
> I fail to see why namespaces would have to be per Thread. I understand
> my code and what methods are available as a lexical issue. Similar as
> when I require a file, the provided classes will be available in every
> thread, I'd expect a change in a namespace to be local to the lexical
> scope of the namespace.
>
> Is there a good example of where and why per-thread namespaces would
> make it that much more useful you imply, so I can follow your argument
> of thread unaware namespaces being worthless?
They wouldn't "have to be" but that would probably be the most useful.
If I have a namespace that changes String#to_s and I call a library that
calls String#to_s, don't I want that library to see my change?
Your version and Trans's example would work fine for very localized
namespacing, which would have much lower implementation impact (and may
also be useful).
> Also I fail to see how lookup would become more complicated. Take a look
> at the approach I took. I copy the classes (on interpreter level one
> could use COW techniques to reduce memory usage) into the new namespace.
> The numbers of levels to look a method up or the way to look it up does
> in no way change. Or do I miss something there?
The complication is that under normal circumstances a method has no
knowledge of whether it's being called inside a namespace or not, since
the installation of the namespace itself is just another method call. So
every method in the system would have to check whether they are being
called under a namespace.
Your code gets around that by essentially delaying the parse until a
namespace is already installed. While this works, and allows namespacing
within that subcontext, you lose all the benefits of having code only
get parsed once. eval is *very* expensive, even more expensive than
installing per-call namespace checks throughout the system.
- Charlie