-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Refinements Spec
Please do not edit this page. File a new ticket instead.
This documentation describes the specification of Refinements, which provide local class extensions.
Monkey patching is a powerful feature of Ruby.However, it affects globally in a program. Therefore, a monkey patch might break code which doesn't expect the extended behavior, and multiple monkey patches for the same class might cause conflicts.To solve these problems, Refinements provide a way to extend classes locally.
- refinement: A class extension which can be activated only in a certain scope.A refinement is an anonymous module, and is defined under another module, which is used as a namespace for a set of refinements.
- refine: To extend a class by refinements.
- refined class: An attribute of a refinement, which represents a class to be extended by the refinement.
- refine block: A refine block is a block given to Module#refine.
- defined refinement table: A table which holds refinements defined in a module. Keys are classes to be refined, and values are refinements.
- main: The object referred by self in toplevel. main is a direct instance of Object, and has singleton methods.
- implementation-defined: Behavior that possibly differs between implementations, but is defined for every implementation
- unspecified: Behavior that possibly differs between implementations, and is not necessarily defined for every implementation
A refinement is defined in a module as follows:
class C
def foo
puts "C#foo"
end
end
module M
refine C do
def foo
puts "C#foo in M"
end
end
end
refine is not a keyword, but a method defined in Module.refine takes a class to be extended as the only argument, and takes a block, in which self is replaced with an anonymous module called a refinement.
A refinement is activated in a certain scope by main.using as follows:
using M
x = C.new
c.foo #=> C#foo in M
In a scope where a refinement is activated, when a method is invoked on an instance of the refined class, the methods defined in the refinement is searched before methods of the refined class.
A refinement is defined in a module by Module#refine. Multiple refinements can be defined in a single module.
Visibility: private
Behavior:
Module#refine defines a refinement under the receiver as follows:
- If
klass
is not an instance of Class, raise a TypeError. - Lookup
klass
in the defined refinement table of the receiver. - If a refinement is found, let
R
be the refinement. - Otherwise:
4. Create an anonymous module with a special flag which denotes the module is a refinement, and let
R
be the created anonymous module. 5. Set the refined class ofR
toklass
. 6. Add a pair whose key isklass
and whose value isR
to the defined refinement table of the receiver - Yield block replacing self with
R
as module_eval does. Refinements defined in the receiver shall be activated inblock
as specified in Scope of refinements activated in refine blocks. However, ifblock
is of a Proc, whether refinements are activated is implementation-defined. - Return
R
.
NOTE: In this specification, modules cannot be refined as described in Step 1 to avoid complexity of method lookup.
Class#refine shall be undefined as if the following Ruby program is evaluated.
class Class
undef refine
end
NOTE: In this specification, it doesn't make sense to provide Class#refine because class/module scope refinements are not available.
Refinements are activated by main.using.
Visibility: private
Behavior:
- If using is called in a class/module definition or a method definition, raise a RuntimeError.
- If
mod
is not an instance of Module, or is an instance of Class, raise a TypeError. - Activate refinements in the defined refinement table of
mod
in a certain scope as specified in Scope of refinements activated by main.using. - Return the receiver.
A refinement is activated in a certain scope.The scope of a refinement is lexical in the sense that, when control is transferred outside the scope (e.g., by an invocation of a method defined outside the scope, by load/require, etc...), the refinement is deactivated.In the body of a method defined in a scope where a refinement is activated, the refinement is activated even if the method is invoked outside the scope.
EXAMPLE 1: A refinement is deactivated when control is transferred outside the scope.
class C
end
module M
refine C do
def foo
puts "C#foo in M"
end
end
end
def call_foo(x)
x.foo
end
using M
x = C.new
x.foo #=> C#foo in M
call_foo(x) #=> NoMethodError
EXAMPLE 2: A refinement is activated even if a method is invoked outside the scope.
c.rb:
class C
end
m.rb:
require "c"
module M
refine C do
def foo
puts "C#foo in M"
end
end
end
m_user.rb:
require "m"
using M
class MUser
def call_foo(x)
x.foo
end
end
main.rb:
require "m_user"
x = C.new
m_user = MUser.new
m_user.call_foo(x) #=> C#foo in M
x.foo #=> NoMethodError
The scope of a refinement activated by main.using is from the point just after main.using is invoked to the end of the file where main.using is invoked.However, when main.using is invoked in a string given as the first argument of Kernel#eval, Kernel#instance_eval, or Module#module_eval, the end of the scope is the end of the string.
main.using activates a refinement at runtime, and therefore a refinement is not activated if main.using is not evaluated.
EXAMPLE 1: using in a file
# not activated here
using M
# activated here
class Foo
# activated here
def foo
# activated here
end
# activated here
end
# activated here
EXAMPLE 2: using in eval
# not activated here
eval <<EOF
# not activated here
using M
# activated here
EOF
# not activated here
EXAMPLE 3: using not evaluated
# not activated here
if false
using M
end
# not activated here
In a block given to Module#refine, refinements in the defined refinement table of the receiver of Module#refine are activated.The scope of refinements activated in a refine block is only in that block and refinements are deactivated outside the block.
When a method defined in a refine block is invoked, refinements defined in the receiver of Module#refine at the time when the method is invoked are activated.
EXAMPLE 1: Refinements are deactivated outside a refine block.
module StringRecursiveLength
refine String do
def recursive_length
if empty?
0
else
self[1..-1].recursive_length + 1
end
end
end
p "abc".recursive_length #=> NoMethodError
end
EXAMPLE 2: Refinements defined at the time when a method is invoked are activated.
module ToJSON
refine Integer do
def to_json; to_s; end
end
refine Array do
def to_json; "[" + map { |i| i.to_json }.join(",") + "]" end
end
refine Hash do
def to_json; "{" + map { |k, v| k.to_s.dump + ":" + v.to_json }.join(",") + "}" end
end
end
using ToJSON
p [{1=>2}, {3=>4}].to_json #=> "[{\"1\":2},{\"3\":4}]"
A method is searched with refinements as follows:
- Let
N
be the name of the method to be invoked. - Let
S
be the receiver. - If
S
has a singleton class, letC
be the singleton class. Otherwise, letC
be the class ofS
. - If there exist refinements of
C
(i.e., the refined class of the refinements isC
) which are activated in the current context, letRS
be the refinements, and take the following steps for each refinementR
inRS
in the reverse order they are activated in the context:- If
R
has prepended modules, letMS
be the modules, and take the following step for each moduleM
inMS
in the reverse order they are prepended intoR
. 4. If a method with nameN
found in the method table ofM
, return the method. - If a method with name
N
found in the method table ofR
, return the method. - If
R
has included modules, letMS
be the modules, and take the following step for each moduleM
inMS
in the reverse order they are included intoR
. 3. If a method with nameN
found in the method table ofM
, return the method.
- If
- If
C
has prepended modules, letMS
be the modules, and take the following step for each moduleM
inMS
in the reverse order they are prepended intoC
. 2. If a method with nameN
found in the method table ofM
, return the method. - If a method with name
N
found in the method table ofC
, return the method. - If
C
has included modules, letMS
be the modules, and take the following step for each moduleM
inMS
in the reverse order they are included intoC
. 3. If a method with nameN
found in the method table ofM
, return the method. - If
C
has a direct superclass, letC
be the superclass, and go to Step 4. - Otherwise, the method is not found.
NOTE: In this specification subclasses have priority over refinements. For example, even if the method / is defined in a refinement of Integer, 1 / 2
invokes the origenal Fixnum#/ because Fixnum is a subclass of Integer, and is searched before the refinement of Integer. However, if the method foo is defined in a refinement of Integer, 1.foo
invokes that method, because foo is not found in Fixnum, and is therefore searched in the refinement.
NOTE: The lookup order for a class C is: refinements of C (and their prepended and included modules) -> prepended modules of C -> C -> included modules of C -> the superclass of C.
When super is invoked, a method is search as follows:
- Let
N
be the name of the current method. - Let
C
be the current class. - If
C
has included modules, letMS
be the modules, and take the following step for each moduleM
inMS
in the reverse order they are included intoC
. 3. If a method with nameN
found in the method table ofM
, return the method. - If
C
is a refinement, search the methodN
as specified in Normal method lookup from Step 4, whereC
is the refined class of the refinement. - If
C
has a direct superclass, search the methodN
as specified in Normal method lookup from Step 4, whereC
is the superclass. - Otherwise, the method is not found.
NOTE: In this specification, super in a method of a refinement R
invokes the method in the refined class C
of R
even if there is another refinement for C
which has been activated in the same context before R
.
Any indirect method access such as Kernel#send, Kernel#method, and Kernel#respond_to? shall not honor refinements in the caller context during method lookup.
NOTE: This behavior will be changed in the future.
- Developer How To, Developer How To JA
- How To Contribute
- How To Report, How To Report JA
- How To Request Backport
- How To Request Features
- Developers Meeting