Author: Shugo Maeda(@shugomaeda) Translator: Makoto Inoue(@makoto_inoue)
In this article, I will talk about Refinements which were supposed to be officially included as part of Ruby 2.0
Ruby has a feature to extend a class by redefining it. It’s called an “open class” or “monkey patching”. It’s widely used in Rails, but overusing it may cause unexpected problems.
For example, requiring “mathn” that comes as part of standard library globally changes the behaviour of Fixnum#/
Refinements are intended to restrict the impact of monkey patching to a certain scope, and I proposed it.
Module#refine extends the class that is passed in as an argument
Inside the refine block, “self” is set to an anonymous module called refinement, and you can add methods to the anonymous module.
The following is how you use it.
This extension is only useful within the file; it does not affect other files.
The specification of Refinements is described in the Wiki
It may contain some bugs as I haven’t received much feedback.
Refinements were supposed to be the big features in Ruby 2.0. However, they ended up as an experimental feature after stripping away a lot of functionality. (At one point, I suggested we remove the feature completely.)
Specifying “refine” or “using” will display a warning because they are still experimental features.
Please use them at your own risk (Don’t complain to me if the feature changes in future).
As mentioned earlier, much functionality has been removed since the initial proposal.
Here is a list of the deletions:
The removal of the last feature has the biggest impact, since it is not as easy to use refinements in an internal DSL.
If we had this functionality, we might have had a feature activerecord-refinements that was implemented by Akira Matsuda.
The above code would let you change the behaviour of an instance of the existing class (Symbol in this case) only within the block.
I was very disappointed because I was thinking about the following library using these features.
(Did you just say you were glad that this functionality was removed???)
If you read the specification in detail, you may have spotted that you can do similar things at the string level (not in the block level) by doing
eval(“using M; #{s}”, TOPLEVEL_BINDING)
However, this is not good enough because you cannot pass in local variables.
We’ve received the following criticism when introducing Refinements
Regarding 1, it’s the nature of refinements
Regarding 2, I created the Wiki page I mentioned earlier. I also added some specs to RubySpec, though they may not have been sufficient (In fact, it was harder to add Ruby 2.0 changes in RubySpec)
Regarding 3, ‘module_eval’ does play nicely with JRuby’s inline caching. I won’t go into detail on this for now.
Regarding 4, I’m sure there are many opinions.
I remember that there were other arguments. In the end, these arguments happened right before the release and therefore we didn’t have time to address all the issues.
I have so far proposed “continuation” and “protected” (I’m sure I’ve made other useful proposals, but can’t remember all of them.)
I wonder if Refinements will be “never two without three” or “third time lucky”.
I am passionate about proposing interesting yet odd features. Stay tuned for my next proposal.
A programmer born in 1975. My favourite music album is “Now and Then” (the cover of “Give Me the Night” is cool) released in 30th Jan by Cloudberry Jam and “Lua no Ceu Congadeiro” by Yuri Popoff
URL: http://shugo.net