Feature #7486
closedCutting through the issues with Refinements
Description
=begin
In issue #4085, there has been a long somewhat contentious discussion about Refinements. While it seems that everyone agrees they have merit, no one seems to have a concrete idea about how they should actually work. There are all sorts of complicated questions and edge case being flailed about. And though Matz is determined to stick to the feature freeze and include Refinements in Ruby 2.0, he has had to greatly peel back there capabilities and scope --it's quite a large change this close to release, and no one is still at all sure that this new limited design is right either. All this is rather unfortunate, not just because it means Ruby 2.0 is probably going to have a half-baked feature that is certain to change substantially by 2.1, but even more so because we were having pretty much the very same discussion six years ago!
In late 2003, there was a long discussion about method wrapping and aspect-oriented programming (AOP) on ruby-talk[1,2,3]. The conversation grew out of an early notion Matz (and maybe Jim Weirich?) had about wrapping methods for Rite. Remember Rite? That was the codename of the original Ruby 2.0. Back then Matz offered up the idea of using (({:pre})) and (({:post})) hooks to wrap methods. The notation was something like:
class C
def foo
print "foo"
end
def foo:pre
print "before"
end
def foo:post
print "after"
end
end
C.new.foo #=> "beforefooafter"
Many of the same questions were asked about these method "hooks" that are now being asked about refinements --"do they stack"", "what happens if we remove the main method", "how are they applied to the object hierarchy?", and so on. While at first glance these yesteryear method hooks and today's refinements may seem quite different, they are actually quite related, which will become clear in a moment.
It was through these threads that Peter Vanbroekhoven and myself began an extensive conversation on AOP for Ruby, based originally on his idea of method wrapping via a module in much the same way as one uses include. He originally called the method that handled this simply "wrap". We know it today as (({prepend})). So you can thank Peter for that whole idea[4]. So, we were both very interested in the concept of AOP and with these early notions in mind we decided to take our conversation off-list with the hope of working out the ideal design for bringing AOP to Ruby. Truth is, we did even better than that.
Peter and I continued to discuss AOP over the following year trading hundreds of communiques exploring every nook and cranny of the concept. Indeed, at a certain point I think Peter was quite tired of it, as I had the tendency to review a concept again and again and again just to make sure we didn't miss anything. But as long and drawn out and as detailed as the whole process was, I think we were far the better for it. We developed a very good understanding of the whole matter. In the course of these conversations, I came upon the idea of the ((transparent subclass)). It was little more than a variation on Peters original wrap idea but it had all the hallmarks of a fundamental OOP concept. With further discussion we agreed that this was a solid corner stone upon which to lay AOP --and not just for Ruby, but for OOP in general! I gave it a name, the "Cut".
From there Peter and I toiled to write an RCR (Ruby Change Request) to introduce the concept to the Ruby community. (Yes, in those days there was such a thing.) We wrote and edited our RCR on the old RubyGarden.org wiki. After many dozens of revisions and over a year after our original discussion!, we finally had our proposal. To top it off Peter even put together a preliminary implementation patch for Ruby, as I put together a pure Ruby (and thus limited) demonstration library. You can find that code and the ((<RCR|http://rubyworks.github.com/cuts/rcr.html>)) today on ((<github|http://rubyworks.github.com/cuts>)).
Now all of this pre-story leads up to what I want to suggest now. I would like the Cuts RCR to be reconsidered[5], on the merits that it is precisely the well thought out, solid foundation, with a real OOP design, that can serve as the basis of implementation for both prepend and refinements, as well as all other aspect-oriented designs patterns developers wish it construct.
So how does this work? How can prepend and refinements be implemented via cuts?
By simple analogy, prepend is to include as cuts are to classes. What this means implementation-wise is that just as modules are included in the class hierarchy via proxy classes, modules would be prepended into the hierarchy via proxy cuts. Its a simple symmetry that provides the proper behavior.
For refinements we need only add a conditional proviso to cuts --a cut would only be applicable if the pertinent scope is using
the refinement. The condition could even be customizable for other uses lending a great deal of flexibility, power and convenience in aspect-oriented designs. To make this idea clear, here is example code for how a cut can be used as a refinement:
cut MyRefinement < String
def self.apply?(binding)
binding.using?(self)
end
def titlecase
gsub(/\b\w/){ $`[-1,1] == "'" ? $& : $&.upcase }
end
end
This is just a normal cut as described in the RCR, but we have added the idea of an ((applicable callback)) --a condition that determines if the cut is used or skipped-over in the method chain. What binding.using?
checks exactly is up to Matz. Most recently Matz has said that the scope should be per-file, but it can just as easily be at a lower level, say pre-module, and it all works --because, cuts themselves have a well defined behavior.
There would be no more confusion about how refinements are supposed to work. Cuts provide the well-defined answer.
[1] ((URL:http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/86071))
[2] ((URL:http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/86391))
[3] ((URL:http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/86646))
[4] Not Yahuda Katz, who Matz has erroneously credited is his recent keynote speeches.
[5] Excluding the idea of the aspect given at the end of the RCR, that can be done via a 3rd party gem.
=end