Bug #7269

Refinement doesn't work if using locate after method

Added by Koichi Sasada over 1 year ago. Updated over 1 year ago.

[ruby-core:48773]
Status:Closed
Priority:Normal
Assignee:Shugo Maeda
Category:core
Target version:2.0.0
ruby -v:ruby 2.0.0dev (2012-11-01 trunk 37404) [i386-mswin32_100] Backport:

Description

Refinement doesn't work if using locate after method.
(I eliminate discussion because my laptop doesn't have enough power...)

class C
def foo
p :C_foo
end
end

module M1
refine C do
def foo
p :M1_foo
super
end
end
end

module M2
refine C do
def foo
p :M2_foo
super
end
end
end

class D
using M1

def x
C.new.foo
end

using M2
end

p :x
D.new.x

#=>
:x
:M1foo
:C
foo

History

#1 Updated by Charles Nutter over 1 year ago

I don't like the idea that using should affect methods already defined. At this point I view using similar to private/protected/etc, which also do not affect methods defined before you call them.

#2 Updated by Koichi Sasada over 1 year ago

(2012/11/03 10:11), headius (Charles Nutter) wrote:

I don't like the idea that using should affect methods already defined. At this point I view using similar to private/protected/etc, which also do not affect methods defined before you call them.

I feel that using' is similar toinclude' and prepend' rather than
public' and `private'.

--
// SASADA Koichi at atdot dot net

#3 Updated by Koichi Sasada over 1 year ago

(2012/11/03 10:36), SASADA Koichi wrote:

(2012/11/03 10:11), headius (Charles Nutter) wrote:

I don't like the idea that using should affect methods already defined. At this point I view using similar to private/protected/etc, which also do not affect methods defined before you call them.

I feel that using' is similar toinclude' and prepend' rather than
public' and `private'.

The following code (C_User2#x) affect refinement `after' defining
method. Is that intentional?

###

class C
def foo
p :C_foo
end
end

module RefineC
refine C do
def foo
p :RefineC_foo
super
end
end
end

class C_User
using RefineC
def x
C.new.foo
end
end

class C_User2
def x
self.class.send(:using, RefineC)
C.new.foo
end
end

puts "C.new.foo"
C.new.foo

puts "CUser.new.x"
C
User.new.x

puts "CUser2.new.x"
C
User2.new.x

#=>
C.new.foo
:Cfoo
C
User.new.x
:RefineCfoo
:C
foo
CUser2.new.x
:RefineC
foo
:C_foo

--
// SASADA Koichi at atdot dot net

#4 Updated by Koichi Sasada over 1 year ago

(2012/11/03 10:42), SASADA Koichi wrote:

###

class C
def foo
p :C_foo
end
end

module RefineC
refine C do
def foo
p :RefineC_foo
super
end
end
end

class C_User
using RefineC
def x
C.new.foo
end
end

class C_User2
def x
self.class.send(:using, RefineC)
C.new.foo
end
end

puts "C.new.foo"
C.new.foo

puts "CUser.new.x"
C
User.new.x

puts "CUser2.new.x"
C
User2.new.x

#=>
C.new.foo
:Cfoo
C
User.new.x
:RefineCfoo
:C
foo
CUser2.new.x
:RefineC
foo
:C_foo

# is that corner case? :)

class C_User2
def x
2.times{
C.new.foo
self.class.send(:using, RefineC)
}
end
end

--
// SASADA Koichi at atdot dot net

#5 Updated by Charles Nutter over 1 year ago

On Sat, Nov 3, 2012 at 10:45 AM, SASADA Koichi ko1@atdot.net wrote:

is that corner case? :)

class C_User2
def x
2.times{
C.new.foo
self.class.send(:using, RefineC)
}
end
end

I commented on the other bug about how refinements need to be temporal
to limit their impact (implementation-wise), but there are obvious
flaws in making them temporal too. Your example above shows how
ordering can change what method will be called. My example was using
calls that happen up-hierarchy in different files.

Even if we ignore implementation/performance concerns (and there are
lots of them), there are many behavioral problems like this with
refinements.

#6 Updated by Shugo Maeda over 1 year ago

headius (Charles Nutter) wrote:

I commented on the other bug about how refinements need to be temporal
to limit their impact (implementation-wise), but there are obvious
flaws in making them temporal too. Your example above shows how
ordering can change what method will be called. My example was using
calls that happen up-hierarchy in different files.

Even if we ignore implementation/performance concerns (and there are
lots of them), there are many behavioral problems like this with
refinements.

I've started to wonder if it's better to limit refinements based on modules instead of lexical scopes.

In the current implementation a cref has a table for activated refinements, but in module-based refinements
a module linked from the cref have the table.
Furthermore, in the current implementation the table is shared and copied-on-write by nested crefs, but it may
be better to separate them and search refinements in outer modules when a method is not found in the table of the current module.

For example,

modue Foo
using X
# Foo's table has refinements in X.
module Bar
using Y
# Bar's table has refinements in Y, not X.

C.new.foo # First, Bar's table is searched, then Foo's table is searched.

end
end

In module-based refinements, toplevel using should affects not only the current file, but global.

#7 Updated by Yukihiro Matsumoto over 1 year ago

OK, I understand the behavior. What are pros and cons of module based refinement?

Matz.

#8 Updated by Charles Nutter over 1 year ago

Making refinements less lexical seems like the wrong direction to me. It means all calls everywhere have to check for refinements all the time, similar to the module_eval problem.

In the following code:

a.rb:

module Foo
def self.go
C.new.foo
end
end

b.rb:

module Foo
using X
end

I assume that regardless of the order of loading these two files, calling the "go" method would require searching the refinements table of Foo, correct? That seems pretty clearly to indicate that module-based refinements require all call sites everywhere to check for refinements every time.

#9 Updated by Charles Nutter over 1 year ago

I should also mention that if we can't tell ahead of time that a call site has to search refinements, it means all calls everywhere will have to have cref available. In JRuby, this could easily be a crippling blow to performance, and in MRI it would make it impossible to eliminate Ruby call frames (or eliminate cref management) ever in the future (which I'm sure ko1 would like to be able to do).

Currently JRuby only needs the cref to be available if there's a closure, binding-related call, or constant lookup. We'd now have to make it available 100% of the time.

It also means that you will never again be able to look at a piece of code and know if refinements will affect it. Refinements would become one of the most confusing features in Ruby.

After talking with Yehuda a bit, I am more and more of the opinion that "using" should only affect call sites lexically in the same scope as (or a child scope of) the "using" call, and only call sites that appear after the "using" call. This would mean having to use "using" in every file where you want refinements active, but it would make it very where refinements are active both to the programmer and to the VM.

#10 Updated by Shugo Maeda over 1 year ago

matz (Yukihiro Matsumoto) wrote:

OK, I understand the behavior. What are pros and cons of module based refinement?

pros:

  • not affected by the order of loading/using
  • consistent with module_eval and behavior when a module is reopened (Bug #7271)

cons:

  • might be hard to implement efficiently
    • especially super when refinements are cascaded might be hard to implement

#11 Updated by Shugo Maeda over 1 year ago

  • Status changed from Open to Closed

Module#using is removed, so I close this ticket.

See https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/RefinementsSpec for scoping of main.using.

Also available in: Atom PDF