Hello,

On Sat, Dec 4, 2010 at 11:56 AM, Yukihiro Matsumoto <matz / ruby-lang.org> wrote:
> |For the main cases, I see it working this way:
> |
> |* Parser sees "using" in a scope
> |* Child scopes will now be parsed as though they are refined
> |* VCALL, FCALL, CALL in child scopes are instead RVCALL, RFCALL, RCALL
>
> I see this optimization nice for refinement, but it's impossible to
> implement local rebinding (a la classbox) that some wanted in this
> thread this way.

Yes, and that is a primary reason why I would not include local
rebinding if I were implementing refinements. I don't see how it's
possible to do without adding overhead to every call (to constantly
check if there's a refinement active) and forcing the global cache to
be flushed repeatedly (since a refinement can be applied from any
scope in any thread at any time). In essence, with local rebinding,
all method calls would have to:

* Ping on every call to see if a refinement is active, since it's not
possible to narrow the refinement to only locally-rebound methods.
* If a refinement is active, check to see if any active refinements
affect them. This would at minimum require inspecting the refinement
to see if it includes methods for the target object's class.
* Either only cache refined methods within that one activation or
don't cache methods at all, since other calls shouldn't see refined
methods in the global cache.
* Do all this in a way that is thread-safe, does not modify any
globally-visible state, and only impacts the activation where
refinements are active.

So at a minimum, all calls in the system would have to constantly ping
*additional* some global state. Because any refinement anywhere would
modify that state, any time any thread does a local rebinding, all
calls would have to check to see if they are affected. And refined
code would require at least adding extra checks to all method cache
validations (to ensure what was cached was not from a refinement not
active in the current activation) or require that refined code never
cache (or only cache within that activation, which would only be
useful if those call sites are encountered many times in a single
activation).

For a real-world example of how local rebinding causes problems, look
at Groovy. Groovy allows local rebinding down-thread; i.e. if a
"category" is active, all invocations deeper in that thread must check
the thread-local category to see if they are affected by it. As a
result:

* All calls everywhere have to constantly check thread-local state to
see if a category is active
* Calls down-stream from a category have to check whether the category
affects them
* Calls down-stream from a category that are affected by it have to do
a slow-path uncached method lookup every time

The end result of Categories in Groovy are:

* All performance is affected by them, and fixing it is difficult
* Users don't use them anymore, since the performance of calls
down-stream from the category are much slower than regular calls
* The Groovy team generally regrets that they were added, due to the
long-term effects

They are considered (by most folks I've talked to) to be a mistake,
but now there's no going back. It's interesting to note that the
Groovy approach at least only affects one thread, and there are no
concurrency issues; only the categorized thread sees the change, and
since it's explicitly in thread-local state, there's no impact across
threads. But as in Refinements, the presence of Categories as a
feature means all calls have to be slower. And even worse, as Groovy
has moved forward with more and more optimizations, two things have
made it difficult: open classes and categories. The latter could have
been avoided.

A Refinements implementation that adds overhead to all calls will
damage Ruby (or at least, Ruby's chances of being a high-performance
language) forever.

- Charlie