Feature #9704

Refinements as files instead of modules

Added by Thomas Sawyer over 2 years ago. Updated over 2 years ago.



If refinements are to remain file-scoped, then it would be more convenient if using worked at the file level, akin to require, rather than behave like a module include. For instance, instead of:

# foo.rb
module Foo
  refine String do
    def some_method
require 'foo'
using Foo

We could do:

# foo.rb
class String
  def some_method
using 'foo'

This would make require and using, in a certain sense, polymorphic --if we require it will extend the classes directly, but if using then they will be refined instead.


#1 [ruby-core:61881] Updated by Yukihiro Matsumoto over 2 years ago

So your key idea is sharing implementation between monkey-patching and refinements.
The motivation behind introducing refinements is discouraging monkey-patching. So it is not attractive for me to something that helps monkey-patching.

What is your intention behind the proposal? Maybe from monkey-patching to refinements transition?


#2 [ruby-core:61882] Updated by Thomas Sawyer over 2 years ago

Yes, the transition from monkey-patching to refinements is a major part of the intention.

But I also do not expect monkey-patching to ever completely go away. (Do you?) Monkey patching is more convenient, in that it can be done via one require for an entire project, where as refining has to specified per-file. So there will be cases where monkey-patching is still preferred.

Besides personal projects and small end-user tools, a good example, I think, is ActiveSupport. While some of it could be used as refinements, I suspect much of it will remain core extensions b/c it represents Rails' DSL, so to speak. And the parts that can become refinements, I would imagine users would still have an option to load them in as extensions.

Even so, I think the main intent is to ask the question: Do refinements need to be modules? If there is no compelling reason for them to be so, then making refinements file-based would simplify reuse and allow us to shed unnecessary boiler-plate.

#3 [ruby-core:62128] Updated by Thomas Sawyer over 2 years ago

Just FYI, I created this project I don't much care for what I had to do to implement it, i.e.

  • it's not possible to simply override #using
  • had to use my finder gem to find libraries
  • had to use clunky gsub and eval

But it appears to be working.

#4 [ruby-core:62136] Updated by Thomas Sawyer over 2 years ago

I realized there is a downside to this approach.

# a.rb
require 'b'
class String
  def ab
    self + "a" + b

# b.rb
class String
  def b
    self + "b"

So what would happen with b.rb when using 'a'? Should the require 'b' become a using? If so, then what happens when it requires something that is not an extension, e.g. set?

If that is an unsolvable issue. The only fix, it seems, would be a way to have something like require_or_using which would act according to the initial loading method used. And that starts to look pretty ugly.

#5 [ruby-core:62158] Updated by Shugo Maeda over 2 years ago

Monkey-patching style has another problem that super cannot be supported in monkey patching.

I prefer another approach to extend classes both globally and locally in the same manner.
How about to provide a way to activate refinements globally?

using Foo, global: true
# Refinements in Foo are activated globally.

Module#refine creates a module as a refinement, so it can be globally activated by Module#prepend-ing that module to the target class.

#6 [ruby-core:62160] Updated by Thomas Sawyer over 2 years ago

Ah, attack the problem from the other way round. That's a great idea!

Also available in: Atom PDF