diff options
author | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2013-01-24 00:40:49 +0000 |
---|---|---|
committer | drbrain <drbrain@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2013-01-24 00:40:49 +0000 |
commit | 30a9931bbc96a3ccda672a2cfb0ed0cd883d7c18 (patch) | |
tree | 0224e7bdc05460551d35a19a0636cbec5c344339 /doc/syntax/refinements.rdoc | |
parent | f031aec4233d7a6d4622c048abed3e86eb5dd6c2 (diff) |
* doc/syntax/refinements.rdoc: Added refinements document based on
the specification from the wiki.
* doc/syntax.rdoc: Added link to refinements document.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@38913 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'doc/syntax/refinements.rdoc')
-rw-r--r-- | doc/syntax/refinements.rdoc | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/doc/syntax/refinements.rdoc b/doc/syntax/refinements.rdoc new file mode 100644 index 0000000000..c8581768fd --- /dev/null +++ b/doc/syntax/refinements.rdoc @@ -0,0 +1,240 @@ += Refinements + +Due to Ruby's open classes you can redefine or add functionality to existing +classes. This is called a "monkey patch". Unfortunately the scope of such +changes is global. All users of the monkey-patched class see the same +changes. This can cause unintended side-effects or breakage of programs. + +Refinements are designed to reduce the impact of monkey patching on other +users of the monkey-patched class. Refinements provide a way to extend a +class locally. + +Refinements are an experimental feature in Ruby 2.0. At the time of writing, +refinements are expected to exist in future versions of Ruby but the +specification of refinements may change. You will receive a warning the first +time you define or activate a refinement. + +Here is a basic refinement: + + class C + def foo + puts "C#foo + end + end + + module M + refine C do + def foo + puts "C#foo in M" + end + end + end + +First, a class +C+ is defined. Next a refinement for +C+ is created using +Module#refine. Refinements only modify classes, not modules so the argument +must be a class. + +Module#refine creates an anonymous module that contains the changes or +refinements to the class (+C+ in the example). +self+ in the refine block is +this anonymous module similar to Module#module_eval. + +Activate the refinement with #using: + + using M + + x = C.new + + c.foo # prints "C#foo in M" + +== Scope + +You may only activate refinements at top-level to the end of the file or in a +string passed to Kernel#eval, Kernel#instance_eval or Kernel#module_eval until +the end of the string. + +Refinements are lexical in scope. When control is transferred outside the +scope the refinement is deactivated. This means that if you require or load a +file or call a method that is defined outside the current scope the refinement +will be deactivated: + + 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 # prints "C#foo in M" + call_foo(x) #=> raises NoMethodError + +If a method is defined in a scope where a refinement is active the refinement +will be active when the method is called. This example spans multiple files: + +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) # prints "C#foo in M" + x.foo #=> raises NoMethodError + +Since the refinement +M+ is active in <code>m_user.rb</code> where +<code>MUser#call_foo</code> is defined it is also active when +<code>main.rb</code> calls +call_foo+. + +Since #using is a method, refinements are only active when it is called. Here +are examples of where a refinement +M+ is and is not active. + +In a file: + + # not activated here + using M + # activated here + class Foo + # activated here + def foo + # activated here + end + # activated here + end + # activated here + +In eval: + + # not activated here + eval <<EOF + # not activated here + using M + # activated here + EOF + # not activated here + +When not evaluated: + + # not activated here + if false + using M + end + # not activated here + +When defining multiple refinements in the same module, inside a refine block +all refinements from the same module are active when a refined method is +called: + + 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 # prints "[{\"1\":2},{\"3\":4}]" + +== Method Lookup + +When looking up a method for a class +C+ Ruby checks: + +* If refinements are active for +C+, in the reverse order they were activated: + * The prepended modules from the refinement for +C+ + * The refinement for +C+ + * The included modules from the refinement for +C+ +* The prepended modules of +C+ +* +C+ +* The included modules of +C+ + +If no method was found at any point this repeats with the superclass of +C+. + +Note that methods in a subclass have priority over refinements in a +superclass. For example, if the method <code>/</code> is defined in a +refinement for Integer <code>1 / 2</code> invokes the original Fixnum#/ +because Fixnum is a subclass of Integer and is searched before the refinements +for the superclass Integer. + +If a method +foo+ is defined on Integer in a refinement, <code>1.foo</code> +invokes that method since +foo+ does not exist on Fixnum. + +== +super+ + +When +super+ is invoked method lookup checks: + +* The included modules of the current class. Note that the current class may + be a refinement. +* If the current class is a refinement, the method lookup proceeds as in the + Method Lookup section above. +* If the current class has a direct superclass, the method proceeds as in the + Method Lookup section above using the superclass. + +Note that +super+ in a method of a refinement invokes the method in the +refined class even if there is another refinement which has been activated in +the same context. + +== Indirect Method Calls + +When using indirect method access such as Kernel#send, Kernel#method or +Kernel#respond_to? refinements are not honored for the caller context during +method lookup. + +This behavior may be changed in the future. + +== Further Reading + +See http://bugs.ruby-lang.org/projects/ruby-trunk/wiki/RefinementsSpec for the +current specification for implementing refinements. The specification also +contains more details. + |