[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Question about BlankSlate.reveal

Gregory Brown

1/25/2009 10:31:00 AM

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.

-greg

[0] http://rubybestpra...

### 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

7 Answers

Robert Klemme

1/26/2009 1:56:00 PM

0

2009/1/25 Gregory Brown <gregory.t.brown@gmail.com>:
> 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://rubybestpra...
>
> ### 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?

Cheers

robert


--
remember.guy do |as, often| as.you_can - without end

Gregory Brown

1/26/2009 4:19:00 PM

0

On Mon, Jan 26, 2009 at 8:55 AM, Robert Klemme
<shortcutter@googlemail.com> wrote:

> 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?

I think you missed that the local variable was at the class level.

>> class A
>> a = nil
>> define_method :foo do
?> a ||= self
>> end
>> end
=> #<Proc:0x000585d4@(irb):11>
>> A.new.foo
=> #<A:0x57030>
>> A.new.foo
=> #<A:0x57030>
>> A.new.foo
=> #<A:0x57030>
>> A.new.foo
=> #<A:0x57030>

But I think I have a suitable fix:
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-t...

What do you think?

-greg

--
Technical Blaag at: http://blog.majesticseacr...
Non-tech stuff at: http://metametta.bl...
"Ruby Best Practices" Book now in O'Reilly Roughcuts:
http://rubybestpra...

Robert Klemme

1/26/2009 9:10:00 PM

0

On 26.01.2009 17:18, Gregory Brown wrote:
> On Mon, Jan 26, 2009 at 8:55 AM, Robert Klemme
> <shortcutter@googlemail.com> wrote:
>
>> 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?
>
> I think you missed that the local variable was at the class level.

Right, you stated so in the text and then I got confused by
"unbound_method.bind(self)". :-)

But in that case my remark about binding at class level remains true:
there is no point in binding to a particular instance at class level and
caching that. And your code seems to indicate that you agree with me there.

> But I think I have a suitable fix:
> http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-t...
>
> What do you think?

Looks good to me. I didn't test it though. ;-)

Kind regards

robert

--
remember.guy do |as, often| as.you_can - without end

David A. Black

1/26/2009 11:32:00 PM

0

Hi --

On Mon, 26 Jan 2009, Robert Klemme wrote:

> 2009/1/25 Gregory Brown <gregory.t.brown@gmail.com>:
>> 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://rubybestpra...
>>
>> ### 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.r...
Coming in 2009: The Well-Grounded Rubyist (http://manning....)

http://www.wis... => Independent, social wishlist management!

David A. Black

1/26/2009 11:33:00 PM

0

On Tue, 27 Jan 2009, Robert Klemme wrote:

> On 26.01.2009 17:18, Gregory Brown wrote:
>> On Mon, Jan 26, 2009 at 8:55 AM, Robert Klemme
>> <shortcutter@googlemail.com> wrote:
>>
>>> 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?
>>
>> I think you missed that the local variable was at the class level.
>
> Right, you stated so in the text and then I got confused by
> "unbound_method.bind(self)". :-)

Sorry -- I posted my reply to your earlier post without having noticed
this post.


David

--
David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.r...
Coming in 2009: The Well-Grounded Rubyist (http://manning....)

http://www.wis... => Independent, social wishlist management!

Gregory Brown

1/26/2009 11:35:00 PM

0

On Mon, Jan 26, 2009 at 4:07 PM, Robert Klemme
<shortcutter@googlemail.com> wrote:
> On 26.01.2009 17:18, Gregory Brown wrote:
>>
>> On Mon, Jan 26, 2009 at 8:55 AM, Robert Klemme
>> <shortcutter@googlemail.com> wrote:
>>
>>> 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?
>>
>> I think you missed that the local variable was at the class level.
>
> Right, you stated so in the text and then I got confused by
> "unbound_method.bind(self)". :-)
>
> But in that case my remark about binding at class level remains true: there
> is no point in binding to a particular instance at class level and caching
> that. And your code seems to indicate that you agree with me there.
>
>> But I think I have a suitable fix:
>> http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-t...
>>
>> What do you think?
>
> Looks good to me. I didn't test it though. ;-)
>
> Kind regards
>
> robert
>
> --
> remember.guy do |as, often| as.you_can - without end
>
>



--
Technical Blaag at: http://blog.majesticseacr...
Non-tech stuff at: http://metametta.bl...
"Ruby Best Practices" Book now in O'Reilly Roughcuts:
http://rubybestpra...

Gregory Brown

1/26/2009 11:37:00 PM

0

On Mon, Jan 26, 2009 at 4:07 PM, Robert Klemme
<shortcutter@googlemail.com> wrote:
> On 26.01.2009 17:18, Gregory Brown wrote:
>>
>> On Mon, Jan 26, 2009 at 8:55 AM, Robert Klemme
>> <shortcutter@googlemail.com> wrote:
>>
>>> 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?
>>
>> I think you missed that the local variable was at the class level.
>
> Right, you stated so in the text and then I got confused by
> "unbound_method.bind(self)". :-)
>
> But in that case my remark about binding at class level remains true: there
> is no point in binding to a particular instance at class level and caching
> that. And your code seems to indicate that you agree with me there.
>
>> But I think I have a suitable fix:
>> http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-t...
>>
>> What do you think?
>
> Looks good to me. I didn't test it though. ;-)

Cool. This is what I'm going to go with in my chapter. I emailed Jim
to see if I can get a definitive 'yes, this is a) a bug or b) some
special casing for builder's needs or c) some special sauce that you
missed'. My guess is it's a), but stranger things have happened :)


-greg

--
Technical Blaag at: http://blog.majesticseacr...
Non-tech stuff at: http://metametta.bl...
"Ruby Best Practices" Book now in O'Reilly Roughcuts:
http://rubybestpra...