From: "headius (Charles Nutter)" <headius@...>
Date: 2012-12-02T13:07:48+09:00
Subject: [ruby-core:50468] [ruby-trunk - Feature #4085] Refinements and nested methods


Issue #4085 has been updated by headius (Charles Nutter).


PROBLEM: reflection from within a refined scope

Several folks have stated they believe reflection from within a refined scope should reflect the refinements. I do not agree, based on a definition of refinements as scope-local.

class Avocado
  def tasty?
    true
  end
end

module RefineAvocado
  refine Avocado do
    def call_tasty
      method(:tasty?).call
    end

    def tasty?
      false
    end
  end
end

The argument is that method() here should return the refined version of tasty? (the one that returns false). I don't see any justification for this.

The "method" method is defined on Object:

class Object
  def method; ... end
end

At the point where "method" is called, exactly one refinement is active: the RefineAvocado module which adds "check_tasty" and overrides "tasty?". So "method" dispatches to Object#method.

Object#method is not refined, since it is not defined within a scope where refinements are active. It does not see refinements active in scopes earlier in the call stack, so it produces a reference to the original "tasty?" method.

What logic dictates that Object#method should now always be a refined method that has to inspect the stack?

If Object#method did reflect refinements active in the caller's scope, it would make it possible to get the refined method...but it would make it impossible to get the *original* method. And the same change has been proposed for all reflective access...the refinements basically make it impossible to inspect the original class, because they're in the way.

Giving Object#method and friends stack powers also makes them impossible to wrap. If you put your own code around Object#method, it will *never* see refinements because your wrapper's scope gets in the way. So if we made these class-hierarchy-related methods reflect refinements, you would no longer be able to safely wrap any of the following methods:

Symbol#to_proc
Object#send
Object#method
Object#methods
Object#respond_to?
Module#instance_method
Module#instance_methods
Module#public_instance_methods
Module#protected_instance_methods
Module#private_instance_methods
Module#method_defined?
Module#public_method_defined?
Module#private_method_defined?
Module#protected_method_defined?
Module#public_class_method
Module#private_class_method
Module#public_instance_method
Module#singleton_methods
Module#protected_methods
Module#private_methods
Module#public_methods
defined? logic for methods and super

And this may be just a start.

Instead, I propose that reflection of refinements be exposed directly.

Kernel#active_refinements => array of refinements active in the current scope, similar to Module#nesting

Object#refined_method(active_refinements, name)
  => returns a Method representing the refined method that would be found for the given list of refinements, or nil
Object#send_refined(active_refinements, name, *args)
  => sends the given name + args as though the specified refinements were active

I guess what I'm asking is that you consider a world where all of Ruby is implemented in Ruby, without stack-walking or magic, and don't make that world harder to achieve by adding magic to so many core methods. If people want a way to reflect from within a refined scope, give them those tools; don't change the semantics of the existing tools and give them new powers to inspect the call stack.

PROBLEM: to_proc and other coercions in relation to refinements

Some folks have said they think to_proc should reflect refinements. I also disagree here.

class Lemur
  def scratch
    puts "itchy itchy"
  end
end

module RefineLemur
  refine Lemur do
    def scratch
      "scratchy scratchy"
    end
  end
end

using RefineLemur

ary = [Lemur.new, Lemur.new]
ary.each &:scratch

This last line is essentially the same as &(:scratch.to_proc)

to_proc can be defined like this, in Ruby:

class Symbol
  def to_proc
    return proc {|obj| obj.send self}
  end
end

It should be apparent why to_proc should not reflect refinements: the send call it makes does not live within a refined scope. You are getting a new proc in hand with logic to send that symbol to any object it is passed. Refinements don't enter into it.

If we return to the definition of refinements as scope-local prepends, there's no way to justify making to_proc reflect refinements. The scope where refinements are active leads up to the to_proc and each calls, but NO FURTHER. The to_proc call operates independent of the refined scope and cannot see refinements. Making it "special", so it can see up the call stack, would mean it can't be wrapped anymore, can't be implemented in Ruby. You are giving superpowers to a single core method that NO RUBY CODE CAN MIMIC.

We should not make yet more core class methods that are impossible to wrap or write in Ruby.

PROBLEM: scoping at file level, module level, or some other level

The main benefit from making refinements file-scoped is simplifying the refined lookup process. If refinements must all be specified at the top level, then we have a clear set of active refinements at any given time. If they're activated within scopes, it can get confusing:

using M1 # M1 is active
module Blah
  using M2 # M1, M2 are active?
  module Bubble
    using M3 # M1, M2, M3 are active?
    "string".some_call ...
  end
  using M4 # M1, M2, M4 are active?
end
# M1 is active

The overall effect on virtual-hierarchy method lookup is still basically the same. The refined modules are searched in most-recently-used order. The virtual hierarchy for the some_call call above would be:

[M3] < [M2] < [M1] < String < Object

If we implement it like Shugo's patch, the overlay modules are attached to the call sites as they are encountered, and only those overlays will ever be active at that call site.

So I guess I'm saying the file-level requirement simplifies some things, but doesn't really change anything conceptually.

PROBLEM: Matz's example showing String refinements active in a refine Array block

This example should not work:

module M1
  refine String do
    def foo; puts 'foo'; end
  end

  module M2
    refine Array do
      "string".foo
    end
  end
end

Within the M2 refinement, only one refinement is active: M2. M2 does live within M1, but M1's refinements are not active within the body of M1, and therefore they should not be active within the body of M2 or the refine Array block.

The virtual hierarchy at the point of the "foo" call looks like this because no active refinements affect String: String < Object

I would like to hear justification why the "foo" call above should succeed. It does not fit my mental model of refinements.

PROBLEM: module_eval

Several folks have claimed refinements will only be useful if they can be applied dynamically to module_eval blocks. However, this does not fit any reasonable definition of refinements thus far.

A block lives within a scope, and that scope may or may not have refinements active. You cannot change what refinements the block will see in the same way that you can't change what constants it will see. The following does not work:

module A
  B = 1
end

A.module_eval { B } # => NameError: uninitialized constant B

And the following should not work either:

module A
  refine String do
    def foo; puts 'foo'; end
  end
end

module B
  using A
end

B.module_eval { "string".foo } # => NoMethodError

The foo call does not appear within a refined scope, and therefore it must never reflect refinements. I think it's just as important for this feature to know definitively when it *won't* happen as when it *will* happen, and it should NEVER be possible to force refinements on Other People's Code.

---

More to come as I think about stuff more. Is it possible for me to get access to edit the wiki page? I'd like to try to fill out more details (assuming I've got the details right).
----------------------------------------
Feature #4085: Refinements and nested methods
https://bugs.ruby-lang.org/issues/4085#change-34304

Author: shugo (Shugo Maeda)
Status: Assigned
Priority: Normal
Assignee: matz (Yukihiro Matsumoto)
Category: core
Target version: 2.0.0


=begin
 As I said at RubyConf 2010, I'd like to propose a new features called
 "Refinements."
 
 Refinements are similar to Classboxes.  However, Refinements doesn't
 support local rebinding as mentioned later.  In this sense,
 Refinements might be more similar to selector namespaces, but I'm not
 sure because I have never seen any implementation of selector
 namespaces.
 
 In Refinements, a Ruby module is used as a namespace (or classbox) for
 class extensions.  Such class extensions are called refinements.  For
 example, the following module refines Fixnum.
 
   module MathN
     refine Fixnum do
       def /(other) quo(other) end
     end
   end
 
 Module#refine(klass) takes one argument, which is a class to be
 extended.  Module#refine also takes a block, where additional or
 overriding methods of klass can be defined.  In this example, MathN
 refines Fixnum so that 1 / 2 returns a rational number (1/2) instead
 of an integer 0.
 
 This refinement can be enabled by the method using.
 
   class Foo
     using MathN
 
     def foo
       p 1 / 2
     end
   end
 
   f = Foo.new
   f.foo #=> (1/2)
   p 1 / 2
 
 In this example, the refinement in MathN is enabled in the definition
 of Foo.  The effective scope of the refinement is the innermost class,
 module, or method where using is called; however the refinement is not
 enabled before the call of using.  If there is no such class, module,
 or method, then the effective scope is the file where using is called.
 Note that refinements are pseudo-lexically scoped.  For example,
 foo.baz prints not "FooExt#bar" but "Foo#bar" in the following code:
 
   class Foo
     def bar
       puts "Foo#bar"
     end
 
     def baz
       bar
     end
   end
 
   module FooExt
     refine Foo do
       def bar
         puts "FooExt#bar"
       end
     end
   end
 
   module Quux
     using FooExt
 
     foo = Foo.new
     foo.bar  # => FooExt#bar
     foo.baz  # => Foo#bar
   end
 
 Refinements are also enabled in reopened definitions of classes using
 refinements and definitions of their subclasses, so they are
 *pseudo*-lexically scoped.
 
   class Foo
     using MathN
   end
 
   class Foo
     # MathN is enabled in a reopened definition.
     p 1 / 2  #=> (1/2)
   end
 
   class Bar < Foo
     # MathN is enabled in a subclass definition.
     p 1 / 2  #=> (1/2)
   end
 
 If a module or class is using refinements, they are enabled in
 module_eval, class_eval, and instance_eval if the receiver is the
 class or module, or an instance of the class.
 
   module A
     using MathN
   end
   class B
     using MathN
   end
   MathN.module_eval do
     p 1 / 2  #=> (1/2)
   end
   A.module_eval do
     p 1 / 2  #=> (1/2)
   end
   B.class_eval do
     p 1 / 2  #=> (1/2)
   end
   B.new.instance_eval do
     p 1 / 2  #=> (1/2)
   end
 
 Besides refinements, I'd like to propose new behavior of nested methods.
 Currently, the scope of a nested method is not closed in the outer method.
 
   def foo
     def bar
       puts "bar"
     end
     bar
   end
   foo  #=> bar
   bar  #=> bar
 
 In Ruby, there are no functions, but only methods.  So there are no
 right places where nested methods are defined.  However, if
 refinements are introduced, a refinement enabled only in the outer
 method would be the right place.  For example, the above code is
 almost equivalent to the following code:
 
   def foo
     klass = self.class
     m = Module.new {
       refine klass do
         def bar
           puts "bar"
         end
       end
     }
     using m
     bar
   end
   foo  #=> bar
   bar  #=> NoMethodError
 
 The attached patch is based on SVN trunk r29837.
=end



-- 
http://bugs.ruby-lang.org/