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).