From: "David A. Black" Date: 2009-01-27T08:32:07+09:00 Subject: Re: Question about BlankSlate.reveal Hi -- On Mon, 26 Jan 2009, Robert Klemme wrote: > 2009/1/25 Gregory Brown : >> Hi folks, >> >> I've been working on a chapter for "Ruby Best Practices"[0] about the >> dynamic nature of Ruby, and I initially thought Jim Weirich's >> BlankSlate would be an excellent example (even though 1.9 has >> BasicObject). >> However, when I look at the reveal class method, I found what seems to >> be a fairly major limitation that I am having a hard time glossing >> over in my prose. >> >> The issue is that when you reveal a previously hidden method, the >> UnboundMethod that was stored gets bound to whatever the first >> instance is that calls the revealed method, because Jim's code caches >> the bound_method via a closure. The best 'fix' I got was to drop the >> caching and rebind the method to self on every single method call, but >> I wasn't able to get away with that without saying "This is probably a >> performance nightmare". Does anyone have a better suggested fix? The >> original code for reveal, pulled from the builder gem, is below. > >> From what you write caching a bound method seems a bad idea because it > is instance specific. Caching of an unbound method would seem much > more reasonable (unless, that is, I am missing something). > > On the other hand: > >> [0] http://rubybestpractices.com >> >> ### Code pulled from blankslate.rb in the builder gem. >> >> # Redefine a previously hidden method so that it may be called on a blank >> # slate object. >> def reveal(name) >> bound_method = nil >> unbound_method = find_hidden_method(name) >> fail "Don't know how to reveal method '#{name}'" unless unbound_method >> define_method(name) do |*args| >> bound_method ||= unbound_method.bind(self) >> bound_method.call(*args) >> end >> end >> end > > I do not really see caching that extends the instance on which #reveal > was invoked. There is actually one closure per instance (more > correctly: one closure per call of reveal) and so no global cache. > > Where exactly is your problem? Let's say you do BasicSlate.reveal(:meth). Then you do slate.meth. At that point, bound_method in the closure is the meth method, bound to the object slate. When you then call other_slate.meth, the ||= short-circuits, and bound_method is *still* the unbound method bound to slate (not to other_slat). So every time you call meth, you're calling it on the same instance. David -- David A. Black / Ruby Power and Light, LLC Ruby/Rails consulting & training: http://www.rubypal.com Coming in 2009: The Well-Grounded Rubyist (http://manning.com/black2) http://www.wishsight.com => Independent, social wishlist management!