ruby-changes:26861
From: drbrain <ko1@a...>
Date: Thu, 24 Jan 2013 09:45:47 +0900 (JST)
Subject: [ruby-changes:26861] drbrain:r38913 (trunk): * doc/syntax/refinements.rdoc: Added refinements document based on
drbrain 2013-01-24 09:40:49 +0900 (Thu, 24 Jan 2013) New Revision: 38913 http://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=rev&revision=38913 Log: * doc/syntax/refinements.rdoc: Added refinements document based on the specification from the wiki. * doc/syntax.rdoc: Added link to refinements document. Added files: trunk/doc/syntax/refinements.rdoc Modified files: trunk/ChangeLog trunk/doc/syntax.rdoc Index: doc/syntax/refinements.rdoc =================================================================== --- doc/syntax/refinements.rdoc (revision 0) +++ doc/syntax/refinements.rdoc (revision 38913) @@ -0,0 +1,240 @@ https://github.com/ruby/ruby/blob/trunk/doc/syntax/refinements.rdoc#L1 += 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. + Property changes on: doc/syntax/refinements.rdoc ___________________________________________________________________ Added: svn:eol-style + LF Index: doc/syntax.rdoc =================================================================== --- doc/syntax.rdoc (revision 38912) +++ doc/syntax.rdoc (revision 38913) @@ -26,6 +26,9 @@ Exceptions[rdoc-ref:syntax/exceptions.rd https://github.com/ruby/ruby/blob/trunk/doc/syntax.rdoc#L26 Precedence[rdoc-ref:syntax/precedence.rdoc] :: Precedence of ruby operators +Refinements[rdoc-ref:syntax/refinements.rdoc] :: + Use and behavior of the experimental refinements feature + Miscellaneous[rdoc-ref:syntax/miscellaneous.rdoc] :: +alias+, +undef+, +BEGIN+, +END+ Index: ChangeLog =================================================================== --- ChangeLog (revision 38912) +++ ChangeLog (revision 38913) @@ -1,3 +1,9 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1 +Thu Jan 24 09:40:13 2013 Eric Hodel <drbrain@s...> + + * doc/syntax/refinements.rdoc: Added refinements document based on + the specification from the wiki. + * doc/syntax.rdoc: Added link to refinements document. + Wed Jan 23 16:29:09 2013 Nobuyoshi Nakada <nobu@r...> * win32/win32.c (rb_w32_spawn, rb_w32_aspawn_flags): fix missing -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/