From: The 8472 Date: 2012-11-23T05:54:36+09:00 Subject: [ruby-core:49891] Re: [ruby-trunk - Feature #4085] Refinements and nested methods 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][""] 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.