From: The 8472 Date: 2012-11-21T08:11:20+09:00 Subject: [ruby-core:49781] Re: [ruby-trunk - Feature #4085] Refinements and nested methods On 20.11.2012 03:45, shugo (Shugo Maeda) wrote: > I think optional features of Refinements are as follows: > > A. refinement inheritance in class hierarchies I generally think that class/module inheritance is the wrong propagation strategy for refinements. If you see refinements as monkey patches they are only necessary to get *your own code* working as desired. When you provide an abstract class/module in a library that application code can inherit from, then the application itself may not need the refinements you use to make the internals of your class tick. In other words, module/class inheritance is about inheriting desired *behavior*. Monkey patches may not be desired behavior, they are the dirty internal mechanics that should be hidden from subclasses. There is an axis orthogonal to behavior. It's responsibility. This second axis is generally associated with the module namespaces. E.g. I expect the rails maintainers to be responsible for the ::ActiveRecord namespace and be aware of what their own code is doing. Their own refinements should not pose a problem to them. But they might be for me. If they "need" a String.camelize in their code then they should be able to add it without polluting my code. If I consider it useful I can still include their refinements into my code. Therefore I think that class inheritance should be removed. And if it gets replaced in the future then it should be with submodule based inheritance. The other issue i have with inheritance is that there is no opt-out. This is the very same issue we're trying to fix! If some piece of code monkey-patches Object then the whole application is hit by that modification. Refinements are supposed to prevent this. But what happens if i want to use a module that applies refinements? Then I would get hit by those refinements too. If we want inheritance then we need some way to opt-out of refinements. Consider someone applying "using" to Object itself early on during the application loading process. We would be in the same mess we are in now. Actually. It would be worse, some methods might see the refinements and others don't, depending on their definition time. For now people can use Module.extended/.included if they really want to add refinement inheritance themselves. > B. refinement activation for reopened module definitions > C. refinement activation for the string version of module_eval/instance_eval > D. refinement activation for the block version of module_eval/instance_eval I don't feel strongly about those, but if the module_eval performance really has such a big issue as headius asserts then it might be better to postpone it until a solution has been found. Probably the safest approach for now would be to use the source refinement scope (which is quasi-static) for module_eval by default and add a way to use the target scope (or an explicit scope) later on as needed. If there is any performance impact it would restricted to the target-scoped procs. I think some clever optimizations should be able to eliminate cases where procs flow through consistent code paths, i.e. are always evaluated against the same target refinement scope as usually is the case with DSLs or class-level configurations. > I have used the word "lexical" to describe Refinements, but by the word I've meant just that Refinements doesn't support local rebinding. For example, in the following code, FooExt doesn't affect Bar#call_foo even if it's called from Baz, which is a module using FooExt. > > class Foo > end > module FooExt > refine Foo do > def foo > puts "foo" > end > end > end > class Bar > def call_foo(f) > f.foo > end > end > module Baz > using FooExt > f = Foo.new > f.foo # => foo > Bar.new.call_foo(f) # => NoMethodError > end > > I think it's the most important feature of Refinements. Without it, it's hard to avoid conflicts among multiple refinements. What about cases like module SomeExt refine String do def bar end end end class Foo using SomeExt def self.test1 "".tap(&:bar) end def self.test2 "".tap{|f| f.bar} end end String.bar is only visible inside Foo, but in test1 the Proc is created in .to_proc of Symbol, i.e. on a different stack frame, which shouldn't be able to see bar due to the scoping. Which leads to counter-intuitive results.