-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
endrefine 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 MIn 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
klassis not an instance of Class, raise a TypeError. - Lookup
klassin the defined refinement table of the receiver. - If a refinement is found, let
Rbe the refinement. - Otherwise:
4. Create an anonymous module with a special flag which denotes the module is a refinement, and let
Rbe the created anonymous module. 5. Set the refined class ofRtoklass. 6. Add a pair whose key isklassand whose value isRto the defined refinement table of the receiver - Yield block replacing self with
Ras module_eval does. Refinements defined in the receiver shall be activated inblockas specified in Scope of refinements activated in refine blocks. However, ifblockis 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
endNOTE: 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
modis not an instance of Module, or is an instance of Class, raise a TypeError. - Activate refinements in the defined refinement table of
modin 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) #=> NoMethodErrorEXAMPLE 2: A refinement is activated even if a method is invoked outside the scope.
c.rb:
class C
endm.rb:
require "c"
module M
refine C do
def foo
puts "C#foo in M"
end
end
endm_user.rb:
require "m"
using M
class MUser
def call_foo(x)
x.foo
end
endmain.rb:
require "m_user"
x = C.new
m_user = MUser.new
m_user.call_foo(x) #=> C#foo in M
x.foo #=> NoMethodErrorThe 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 hereEXAMPLE 2: using in eval
# not activated here
eval <<EOF
# not activated here
using M
# activated here
EOF
# not activated hereEXAMPLE 3: using not evaluated
# not activated here
if false
using M
end
# not activated hereIn 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
endEXAMPLE 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
Nbe the name of the method to be invoked. - Let
Sbe the receiver. - If
Shas a singleton class, letCbe the singleton class. Otherwise, letCbe 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, letRSbe the refinements, and take the following steps for each refinementRinRSin the reverse order they are activated in the context:- If
Rhas prepended modules, letMSbe the modules, and take the following step for each moduleMinMSin the reverse order they are prepended intoR. 4. If a method with nameNfound in the method table ofM, return the method. - If a method with name
Nfound in the method table ofR, return the method. - If
Rhas included modules, letMSbe the modules, and take the following step for each moduleMinMSin the reverse order they are included intoR. 3. If a method with nameNfound in the method table ofM, return the method.
- If
- If
Chas prepended modules, letMSbe the modules, and take the following step for each moduleMinMSin the reverse order they are prepended intoC. 2. If a method with nameNfound in the method table ofM, return the method. - If a method with name
Nfound in the method table ofC, return the method. - If
Chas included modules, letMSbe the modules, and take the following step for each moduleMinMSin the reverse order they are included intoC. 3. If a method with nameNfound in the method table ofM, return the method. - If
Chas a direct superclass, letCbe 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 original 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
Nbe the name of the current method. - Let
Cbe the current class. - If
Chas included modules, letMSbe the modules, and take the following step for each moduleMinMSin the reverse order they are included intoC. 3. If a method with nameNfound in the method table ofM, return the method. - If
Cis a refinement, search the methodNas specified in Normal method lookup from Step 4, whereCis the refined class of the refinement. - If
Chas a direct superclass, search the methodNas specified in Normal method lookup from Step 4, whereCis 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