Feature #17380
Useful `include/prepend` in `refine`
Description
Currently, prepend/include
within a refine
block leads to a method not being to see itself, or others defined in the same module:
module Code
def recurse(value = nil)
return value if value
recurse(42) # => NoMethodError!!!
end
end
module Extension
refine Object do
include Code
end
end
using Extension
:x.recurse(:y) # => :y (ok)
:x.recurse # => NoMethodError, was hoping for 42
I find this unintuitive and not useful.
The conclusion of the current situation from shugo (Shugo Maeda) and others is "I don't recommend module inclusion to define refined methods"
Could we change this situation so it can be recommended to use it?
What I believe would be more useful and is what I expected was that include/prepend
within a Module
would bring in the current methods in the Module, with the current refinements activated.
One use-case in particular is to publish libraries where one can give the option to the user to either:
- call
using GreatExtension
in each and every file that need it - or
MyClass.prepend GreatExtension
once.
While Jeremy Evans found a way to do it, it remains challenging and unnatural.
Related issues
Updated by Eregon (Benoit Daloze) 3 months ago
- Related to Bug #17374: Refined methods aren't visible from a refinement's module added
Updated by Eregon (Benoit Daloze) 3 months ago
Maybe we should allow include RefinedImplementation
from https://bugs.ruby-lang.org/issues/17374#note-8 ?
Copying methods manually seems to have a very similar effect, but it would be more convenient.
Updated by Dan0042 (Daniel DeLorme) 3 months ago
It would be nice if prepend
/include
worked within a refine
block, but if they don't then at least it should raise an error. In that respect I disagree with closing #17374; even if the result is "expected", the fact that including a module is effectively a no-op should be considered a bug. Silently failing to have an effect is not so good.
Updated by marcandre (Marc-Andre Lafortune) 3 months ago
Dan0042 (Daniel DeLorme) wrote in #note-3:
including a module is effectively a no-op
It isn't a no-op, as it does bring each method in the refinement, but those methods "live" outside of said refinement. See my example above where :x.recurse(:y) # => :y (ok)
.
Updated by Dan0042 (Daniel DeLorme) 3 months ago
marcandre (Marc-Andre Lafortune) wrote in #note-4:
It isn't a no-op, as it does bring each method in the refinement, but those methods "live" outside of said refinement. See my example above where
:x.recurse(:y) # => :y (ok)
.
Yes, I understand that, but even if the methods are technically in the refinement, if they are unreachable then effectively it's the same as a no-op. Although as the example shows it's more like a "half-op"; the methods are reachable from the outside but not the inside.