On 22.11.2012 17:40, headius (Charles Nutter) wrote:

> After lookup, call sites would know there's potentially refinements active for the given method. The calling scope (or parent scopes) would have references to individual refinements, and if there were an entry for one of them it would be used.
>
> This still requires access to the caller scope, of course, to understand what refinements are active. However, because refinement changes would invalidate the String class directly (since they actually modify the method table), the method (refined or otherwise) could be cached as normal. The caller's scope never changes (statically determined at compile time), so it does not participate in invalidation.
>
> This also works for refinements added to classes after the initial run through. If we cache the default downcase method from String, and then the refinement is updated to add downcase, we would see that as an invalidation event for String's method table. Future calls would then re-cache and pick up the change.
>
> This also feels a bit more OO-friendly to me. Rather than storing patches on separate structures sprinkled around memory, we store the patches directly on the refined class, only using the module containing the refinements as a key. The methods *do* live on String, but depending on the *namespace* they're looked up from we *select* the appropriate implementation. It's basically just double-dispatch at that point, with the selector being the calling context.

This (together with Module.prepend) is reminding me a bit of AspectJ's 
pointcuts. Which in turn leads me to think that we are missing something 
here:
We don't know and cannot know in advance which kind of scopes the 
developer will need to apply his patches.

We have many different ideas flying around how to determine the scope of 
the refinement.

a) Local only? Maybe even constrained inside a block?

Good for builder DSLs or the like where you basically want to extend 
core objects to make it look more like written sentences than code.

b) Class inheritance?

E.g. If you want to provide some nice class configuration syntax

c) Module namespace?

If you like to use some convenience methods throughout your project 
without creating conflicts with extensions that libraries might use in 
their own code

d) Stack-down X frames

Black Magic: Patch some behavior inside a single method by wrapping it

e) Thread local

More black magic: Fix some broken interaction between library code.
Stub any kind of method out temporarily without breaking other things in 
multi-threaded environments.



So what I am saying is that we don't just need a way to define 
refinement namespaces. We also need to let the programmer define where 
and when those namespaces get applied. And we need the common cases to 
be fast. The madness-driven ones (d and e) can be slow, but can only be 
allowed to be slow at those callsites that are affected, not globally.

So I would suggest not providing *any* inheritance at all. Refinements 
scopes must be activated in every single module (or possibly even 
method) that they should be applied to. They shouldn't even apply to 
methods overriding another method when the super-method is 
refinement-scoped. If you want to apply them in many places at once you 
can do so via metaprogramming.

   module FooExt
     refine String do
       def downcase
         upcase
       end
     end
   end



case a)

   class ClassA
     def bar; end

     # apply to all methods when no block is passed
     using(FooExt)

     def baz; end

     # both .bar and .baz are refined now.
     # we can apply them retroactively!
     # this is important for monkey-patching
   end

   class ClassB
     def foo
       "x".downcase => "x"
       using_refinements(FooExt) do
         "x".downcase => "X"
       end
     end
   end

   class ClassC
     def bar
       "x".downcase
     end
     def baz
       "x".downcase
     end
   end

   o = ClassC.new

   o.bar # => "x"
   o.send(:using, FooExt, :on => :bar)
   o.bar # => "X"
   o.baz # => "x"

   # acts as instance_eval with refinements
   Object.new.using_refinements(FooExt) do
     "x".downcase # => "X"
   end


case b)

   class MyModel < ActiveRecord::Base; end
   class SubModel < MyModel; end
   class OtherModel < ActiveRecord::Base; end

   ActiceRecord::Base.descendants.each do |c|
     c.send(:using, FooExt)
   end

case c)

   # assume this traverses the constants downwards
   MyApplicationNamespace.recursive_submodules.each do |mod|
     mod.send(:using, FooExt)
   end

   # only modify callsites for a single method
   Bar.instance_method(:test).using(FooExt)

case d)
case e)

   # applies refinement to callsites in method :bar in MyClass
   # but only if the guard condition is true
   # otherwise the unrefined method is used
   MyClass.send(:using, FooExt, :on => :bar, :if => lambda do
      Thread[:use_string_patches?]
   end)

   # applies FooExt a dynamic refinement scope
   # to *all* String.downcase callsites throughout the application
   FooExt.send(:use_everywhere) do
      Thread[:use_string_patches?] && caller[1]["<stack match here>"]
   end



I think this should demonstrate the power of letting the programmer 
decide how refinement scopes are determined instead of having the 
language dictate a fixed lookup strategy.

Cases d) and e) are just for demonstration and don't have to be taken 
seriously!


But the metaprogramming issues with __send__, respond_to? and 
Symbol.to_proc would still remain.