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.