[lnkForumImage]
TotalShareware - Download Free Software

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


 

Forums >

comp.lang.ruby

Re: Undo Object.extend

Robert Dober

9/2/2008 10:22:00 AM

On Mon, Sep 1, 2008 at 10:34 PM, Jos=E9 Ignacio
<joseignacio.fernandez@gmail.com> wrote:
> Thanks, that's a really nice piece of code that helps a lot :)
>
> Why use a @__bsp__ variable instead of using @__behavior_stack__.pop or
> @__behavior_stack__.last ?
Good question ;). Actually I am leaving empty modules on the stack
that can be reused, thus
after a push_behavior the stack might look like this [ x, module with
the methods from pushed behavior ] and the sp is 1
after pop_behavior the situation is as follows stack : [x, empty
module] and the sp=3D0.
This avoids unnecessary recreation of modules ( and lets the GC rest,
but that was not the idea ;)
>
> It seems that the trick to undo Object.extend is done by erasing all
> instance methods in the modules. Isn't this a bit strange? If Ruby
> allows including modules in an instance's ancestor chain, there should
> be a way to remove them...
I am not sure to understand this question, do you mean removing them
from the includee?
That would not work, sorry if I miss the obvious here maybe you might elabo=
rate.

Cheers
Robert
> --
> Posted via http://www.ruby-....
>
>



--=20
C'est v=E9ritablement utile puisque c'est joli.

Antoine de Saint Exup=E9ry

4 Answers

José Ignacio

9/2/2008 10:55:00 AM

0

I've modified my version by getting inspired by your code :)
In this case there is no stack. You can remove any arbitrary
behaviour/module, which can be an advantage or a risk.

module Behaviourable
def extend mod
@ancestors ||= {}
return if @ancestors[mod]
mod_clone = mod.clone
@ancestors[mod] = mod_clone
super mod_clone
end

def remove mod
mod_clone = @ancestors[mod]
mod_clone.instance_methods.each {|m| mod_clone.module_eval {
remove_method m } }
@ancestors[mod] = nil
end
end

class A
include Behaviourable
end

module NormalBehaviour
def talk; puts 'Hi'; end
end
module EnhancedBehaviour
def talk; super; puts 'Goodbye'; end
end

a = A.new
a.extend NormalBehaviour
a.talk # Hi
a.extend EnhancedBehaviour
a.talk # Hi
# Goodbye
a.remove EnhancedBehaviour
a.talk # Hi
a.extend EnhancedBehaviour
a.extend EnhancedBehaviour # Nothing happens
a.talk # Hi
# Goodbye

Robert Dober wrote:
> On Mon, Sep 1, 2008 at 10:34 PM, Jos� Ignacio
> <joseignacio.fernandez@gmail.com> wrote:
>> Thanks, that's a really nice piece of code that helps a lot :)
>>
>> Why use a @__bsp__ variable instead of using @__behavior_stack__.pop or
>> @__behavior_stack__.last ?
> Good question ;). Actually I am leaving empty modules on the stack
> that can be reused, thus
> after a push_behavior the stack might look like this [ x, module with
> the methods from pushed behavior ] and the sp is 1
> after pop_behavior the situation is as follows stack : [x, empty
> module] and the sp=0.
> This avoids unnecessary recreation of modules ( and lets the GC rest,
> but that was not the idea ;)

Nice :) That's not the case in my code, where objects are created and
deleted everytime you add or remove a behaviour.

>>
>> It seems that the trick to undo Object.extend is done by erasing all
>> instance methods in the modules. Isn't this a bit strange? If Ruby
>> allows including modules in an instance's ancestor chain, there should
>> be a way to remove them...
> I am not sure to understand this question, do you mean removing them
> from the includee?
> That would not work, sorry if I miss the obvious here maybe you might
> elaborate.
>
Trouble is that I don't really know what happens in the background in
Ruby when you use Object.extend.

I suppose an internal module chain is kept in instances in a similar way
as it happens with classes:
A.ancestors # => [A, Behaviourable, Object, Kernel]

a.extend NormalBehaviour # should make something like a.internal_chain =
[NormalBehaviour.clone, A, Behaviourable, Object, Kernel]

If we remove all methods from that NormalBehaviour.clone and re-extend
the object with that module, the chain will grow with an empty module:

a.remove NormalBehaviour # a.internal_chain =
[NormalBehaviour.clone.empty!, A, Behaviourable, Object, Kernel]

a.extend NormalBehaviour # a.internal_chain = [NormalBehaviour.clone,
NormalBehaviour.clone.empty!, A, Behaviourable, Object, Kernel]

But that's my way of viewing this... I don't really know what happens in
the background :)

Cheers
Jose
--
Posted via http://www.ruby-....

Robert Dober

9/2/2008 11:59:00 AM

0

On Tue, Sep 2, 2008 at 12:54 PM, Jos=E9 Ignacio
<joseignacio.fernandez@gmail.com> wrote:
<snip>
> a.extend EnhancedBehaviour
> a.extend EnhancedBehaviour # Nothing happens
That might be a feature or a bug, when pushing behavior dynamically
depending on some events it might be better to push it twice but I am
sure you implemented it how you needed it.
<snip>
> Trouble is that I don't really know what happens in the background in
> Ruby when you use Object.extend.
IIRC
o.extend m
is a shortcut for
class << o; include m end

Cheers
Robert
--=20
C'est v=E9ritablement utile puisque c'est joli.

Antoine de Saint Exup=E9ry

José Ignacio

9/2/2008 1:28:00 PM

0

> IIRC
> o.extend m
> is a shortcut for
> class << o; include m end

I think you're right, after reviewing what Why points out at
http://whytheluckystiff.net/articles/seeingMetaclassesCl... .
This makes me wonder again if there are any performance issues due to
leaving empty modules in the object's metaclass ancestor chain, but that
might depend on Ruby's implementation.
--
Posted via http://www.ruby-....

José Ignacio

9/2/2008 2:05:00 PM

0

Benchmarks come to save us:

require 'benchmark'
module Mod; def method; 3*50+1; end; end
a = A.new; a.extend Mod
b = A.new; b.extend Mod
Benchmark.bm(30) do |x|
x.report('Removing & extending (1)') { 1000.times {b.remove(Mod);
b.extend(Mod)} }
x.report('Removing & extending (2)') { 1000.times {b.remove(Mod);
b.extend(Mod)} }
x.report('Removing & extending (3)') { 1000.times {b.remove(Mod);
b.extend(Mod)} }
x.report('Removing & extending (4)') { 1000.times {b.remove(Mod);
b.extend(Mod)} }
x.report('Removing & extending (5)') { 1000.times {b.remove(Mod);
b.extend(Mod)} }
x.report('Removing & extending (7)') { 1000.times {b.remove(Mod);
b.extend(Mod)} }
x.report('Removing & extending (8)') { 1000.times {b.remove(Mod);
b.extend(Mod)} }
x.report('Removing & extending (9)') { 1000.times {b.remove(Mod);
b.extend(Mod)} }
x.report('Removing & extending (10)') { 1000.times {b.remove(Mod);
b.extend(Mod)} }
x.report('a.method') { 5000000.times {a.method} }
x.report('b.method') { 5000000.times {b.method} }
end

Output:
user system total
real
Removing & extending (1) 0.040000 0.000000 0.040000 (
0.044988)
Removing & extending (2) 0.090000 0.000000 0.090000 (
0.093711)
Removing & extending (3) 0.130000 0.000000 0.130000 (
0.132898)
Removing & extending (4) 0.170000 0.000000 0.170000 (
0.168743)
Removing & extending (5) 0.210000 0.000000 0.210000 (
0.214160)
Removing & extending (7) 0.260000 0.010000 0.270000 (
0.266014)
Removing & extending (8) 0.330000 0.000000 0.330000 (
0.327045)
Removing & extending (9) 0.400000 0.000000 0.400000 (
0.404618)
Removing & extending (10) 0.520000 0.000000 0.520000 (
0.519781)
a.method 5.290000 1.140000 6.430000 (
6.428015)
b.method 5.080000 1.130000 6.210000 (
6.390391)

It's suprising, but removing and extending slows down increasingly,
though there is no slow down in the method call (i.e. a & b have the
same performance).

I've tried to repeat the tests commenting out "super mod_clone" in
Behaviourable.extend and now there's no slow down, so the performance
issue is related with Object.extend.
--
Posted via http://www.ruby-....