From: Charles Oliver Nutter Date: 2010-12-05T10:06:46+09:00 Subject: [ruby-core:33578] Re: [Ruby 1.9-Feature#4085][Open] Refinements and nested methods Hello, On Sat, Dec 4, 2010 at 11:56 AM, Yukihiro Matsumoto 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