From: The 8472 Date: 2012-11-17T12:35:54+09:00 Subject: [ruby-core:49478] Re: [ruby-trunk - Feature #4085] Refinements and nested methods > So, in one sense, refinements are to localize monkey-patching. But they don't actually localize it much better since they can apply at a distance to blocks (module_eval feature), and classes down-hierarchy. > Previously, all code determined what methods to call based solely on the target object's class hierarchy. Even with monkeypatches in place, we still have to look solely at the target class to determine what's being called. > With refinements, every piece of code everywhere in the system could potentially see refinements active whether there's a "using" clause near them or not. Blocks could be forced to call different methods at any time, normal code could see a superclass add a refinement and change all future calls. Refinements may prevent monkeypatches from affecting the entire runtime, but don't make it any easier to determine what methods will actually be called. I think I might have a solution to this: 1. refinements should only go through the local module hierarchy, not the class hierarchy because all contributors to a module namespace should be familiar with the conventions and refinements used within that module. If for example ActiveRecord wants to add .constantize their Strings and use that feature throughout their project it should be fine, as they should be putting everything under the ::ActiveRecord module anyway. If someone has his own rails application he can use the refinement again in his own application module or - if he isn't using any module - apply it to all of his classes. 2. instance_eval/module_eval/class_eval should NOT apply the refinements of the target. Instead they should use the same refinements as the context they were defined in. To still allow for fancy DSLs there should be a way to explicitly rebind the context of the Proc to a different refinement context. Basically bind its lookup to a different module. A Proc passed to the DSL would first have to be re-bound to the DSL's own module and then eval'd. As long as nobody else rebinds the Proc again this shouldn't invalidate any cache as the Proc was never called in its old context before. What is even better is that someone else could extend the DSL under a different module but the DSL's extensions would still get applied to the block, as expected by the caller. 3. there should also be an way to remove refinements from a module and all its submodules. This makes it possible to apply a refinement to ::Object and then prune it out of some foreign namespaces where it turns out to cause trouble. Consider it "optimistic refining". 1 and 2 mean that every Callsite and Proc can only have one refinement context at any given time. Rule 3 makes monkeypatching safer as we can still apply it globally as we already do and simply undo it in places where the monkey bites us. While this may seem less flexible you also have to consider that these single-scope refinements can be used together with Module.prepend This allows you to move an aspect of code to a different module inside your application, apply refinements only to that module (and its submodules), write your refined code inside that module and then prepend that module with the refined behavior to the target class which you do not want to "pollute" with the refinements. > > They also don't solve the monkeypatching problem in any way. Monkeypatches have been used for a few reasons: > > * Adding methods to existing types, for DSL or fluent API purposes. > > Refinements would limit the visibility of those methods, somewhat, but you can't tell without digging around both the target class hierarchy and the calling class hierarchy what methods will really be called. > > * Replacing existing methods with "better" versions > > Refinements would again limit the visibility of those changes, but ultimately result in some code calling one method and some code calling another, with no easy way to determine the code that will be called ahead of time. > > It may be possible to address the technical issues of optimizing call sites with and without refinements, but I really don't feel like refinements are solving as many problems as they're going to create. I lament a future where I can't look at a piece of code and determine the methods it's calling solely based on the types it is calling against. It's going to be harder -- not easier -- to reason about code with refinements in play.