Extending an Instance only Once

I have a method that requires that the arguments passed to it have been in-mixed with a module. Currently, my code looks like this:

def my_method( the_obj )
     the_obj.extend( MyModule ) unless the_obj.inherits_from?( MyModule )
end

class Object
     def selfclass
         class << self; self; end
     end

     # Return true if the instance inherits from the supplied _class_or_module_.
     def inherits_from?( class_or_module )
         self.class.ancestors.include?( class_or_module ) or self.selfclass.ancestors.include?( class_or_module )
     end
end

Is there a better way to do that? (I'm testing if the object inherits from a class which was extended by the module, OR if the instance itself was already extended.) Does it end up being cheaper/faster to just always re-extend the instance? (What happens if you extend both the class and an instance of that class? Does the singleton class take precedence?)

···

--
"When I am working on a problem I never think about beauty. I only think about how to solve the problem. But when I have finished, if the solution is not beautiful, I know it is wrong."
- R. Buckminster Fuller

HI --

I have a method that requires that the arguments passed to it have been in-mixed with a module. Currently, my code looks like this:

def my_method( the_obj )
   the_obj.extend( MyModule ) unless the_obj.inherits_from?( MyModule )
end

class Object
   def selfclass
       class << self; self; end
   end

   # Return true if the instance inherits from the supplied _class_or_module_.
   def inherits_from?( class_or_module )
       self.class.ancestors.include?( class_or_module ) or self.selfclass.ancestors.include?( class_or_module )
   end
end

Is there a better way to do that?

Unless I'm missing something, #is_a? would do it.

(I'm testing if the object inherits from a class which was extended by the module, OR if the instance itself was already extended.) Does it end up being cheaper/faster to just always re-extend the instance? (What happens if you extend both the class and an instance of that class? Does the singleton class take precedence?)

The singleton class takes precedence in the sense that it comes
earlier in the method lookup path. (I'm assuming that you mean mixing
the module into the class, not MyClass.extend(MyMod), which would not
affect the class's instances.)

Overall, I'm tempted to say that if you're extending a single object
multiple times with the same module, there's something not quite right
in the program flow. But I doubt it's going to slow things down very
much (but I haven't benchmarked it).

David

···

On Tue, 17 May 2005, Gavin Kistner wrote:

--
David A. Black
dblack@wobblini.net

I have a method that requires that the arguments passed to it have been in-mixed with a module. Currently, my code looks like this:

def my_method( the_obj )
   the_obj.extend( MyModule ) unless the_obj.inherits_from?( MyModule )
end

Ruby already checks this:

irb(main):001:0> module M ; end
=> nil
irb(main):002:0> class A ; end
=> nil
irb(main):003:0> class B < A; end
=> nil
irb(main):004:0> class A ; include M ; end
=> A
irb(main):005:0> class B ; include M ; end
=> B
irb(main):006:0> B.ancestors
=> [B, A, M, Object, Kernel]

Note that there is a single M in there. If the includes happen in the reverse order, Ruby's check fails and there are two M's in the list:

irb(main):001:0> module M ; end
=> nil
irb(main):002:0> class A ; end
=> nil
irb(main):003:0> class B < A; end
=> nil
irb(main):004:0> class B ; include M ; end
=> B
irb(main):005:0> class A ; include M ; end
=> A
irb(main):006:0> B.ancestors
=> [B, M, A, M, Object, Kernel]

This does not proof the check is actually done, but in the source you can see that check is actually there without doubt.

The same goes for Object#extend because it is implemented as an include in the singleton class (or selfclass as some people prefer :slight_smile: which obeys the same rules as includes in regular classes. So no need to check this yourself!

Peter

Unless I'm missing something, #is_a? would do it.

Nope, apparently it was I who missed something. Thanks :slight_smile:

The singleton class takes precedence in the sense that it comes
earlier in the method lookup path. (I'm assuming that you mean mixing
the module into the class, not MyClass.extend(MyMod), which would not
affect the class's instances.)

Indeed, that is what I meant.
   module M; end
   class Foo; end
   class Bar; include M; end
   f = Foo.new
   b = Bar.new
   f.is_a?( M ) #=> true
   b.is_a?( M ) #=> true

Yay :slight_smile:

Overall, I'm tempted to say that if you're extending a single object
multiple times with the same module, there's something not quite right
in the program flow. But I doubt it's going to slow things down very
much (but I haven't benchmarked it).

Yeah, I could certainly realize _some_ performance gain by ensuring that the consumer of the class ensures that they pre-mix any instances which are handed to the method. But I'm going for uber-friendliness, sort of auto-duck-typing. ("You say you don't quack like a duck? Here, let me strap this bill onto your head.")

When I get to benchmarking I'll check it out, but I think it should be fine, since this method is run only a few times during setup, and not during the cpu-intensive phase.

···

On May 17, 2005, at 5:34 AM, David A. Black wrote:

Hrm...I see code that looks like that's what's going on inside of class.c, but apparently it is faster to test myself:

require 'benchmark'
include Benchmark

module M; end
class Bar < Array; end
class Foo < Bar; end

n = 500000
bm(15){ |x|
     f = Foo.new
     x.report( "Always Extend" ){
         n.times{
             x.extend( M )
         }
     }

     f = Foo.new
     x.report( "Test First" ){
         n.times{
             x.extend( M ) unless x.is_a?( M )
         }
     }
}

                      user system total real
Always Extend 1.610000 0.010000 1.620000 ( 1.983911)
Test First 0.730000 0.000000 0.730000 ( 0.745857)

Is there a flaw with my test above?

···

On May 17, 2005, at 7:01 AM, Peter wrote:

I have a method that requires that the arguments passed to it have been in-mixed with a module. Currently, my code looks like this:

def my_method( the_obj )
   the_obj.extend( MyModule ) unless the_obj.inherits_from?( MyModule )
end

This does not proof the check is actually done, but in the source you can see that check is actually there without doubt.

The same goes for Object#extend because it is implemented as an include in the singleton class (or selfclass as some people prefer :slight_smile: which obeys the same rules as includes in regular classes. So no need to check this yourself!

Hrm...I see code that looks like that's what's going on inside of
class.c, but apparently it is faster to test myself:

require 'benchmark'
include Benchmark

module M; end
class Bar < Array; end
class Foo < Bar; end

n = 500000
bm(15){ |x|
     f = Foo.new
     x.report( "Always Extend" ){
         n.times{
             x.extend( M )

              ^^^

         }
     }

     f = Foo.new
     x.report( "Test First" ){
         n.times{
             x.extend( M ) unless x.is_a?( M )

              ^^^ ^^^

         }
     }
}

                      user system total real
Always Extend 1.610000 0.010000 1.620000 ( 1.983911)
Test First 0.730000 0.000000 0.730000 ( 0.745857)

Is there a flaw with my test above?

You made a typo in your tests (x should be replaced with f), but it
does not influence the result. The only explanation I have is that the
test used in include is more complicated than the one used in #is_a?
because include doesn't only check the module passed as parameter to
#include, it checks modules included in that module too. I guess even
when there are no such modules, there is still some overhead (it's not
exactly a straightforward algorithm). But unless you really intend to
do 500000 extends in a row, I don't think that is really a problem...
:slight_smile:

Peter

peter.vanbroekhoven@gmail.com schrieb:

You made a typo in your tests (x should be replaced with f), but it
does not influence the result. The only explanation I have is that the
test used in include is more complicated than the one used in #is_a?
because include doesn't only check the module passed as parameter to
#include, it checks modules included in that module too. I guess even
when there are no such modules, there is still some overhead (it's not
exactly a straightforward algorithm). But unless you really intend to
do 500000 extends in a row, I don't think that is really a problem...
:slight_smile:

The explanation is probably simpler - f.extend(M) makes hook calls to
M.extend_object(f) and M.extended(f) - M.extend_object(f) probably
tests if f.is_a?(M) a - this means its 4 method calls, versus 1 call + 1 if statement ...
you'll probably find a much smaller difference calling M.extend_object(f) only.

···

---
require 'benchmark'
include Benchmark

module M
class Bar < Array; end
class Foo < Bar; end

n = 500000
bm(15){ |x|
     f = Foo.new
    x.report( "Test First" ){
        n.times{
            extend_object( f ) unless f.is_a?( M )
        }
    }
    f = Foo.new
    x.report( "Always Extend" ){
        n.times{
          extend_object( f )
        }
    }
}
end
---

/Christoph

Peter

Christoph wrote:

The explanation is probably simpler - f.extend(M) makes hook calls

to

···

M.extend_object(f) and M.extended(f) - M.extend_object(f) probably
tests if f.is_a?(M) a - this means its 4 method calls, versus 1 call

+

1 if statement ...
you'll probably find a much smaller difference calling
M.extend_object(f) only.

You're right, of course. I would have noticed that had I actually taken
a look at the code instead of stating things from memory.

M.extend_object(f) doesn't call f.is_a?(M) though; module inclusion
uses its own specialized check. What surprised me a bit though is that
M.extended(f) is called each time, even when f is already extended with
M and is hence not extended again. We could save an extra method call
there (unless people rely on its being called every time Object#extend
is called) and get it down to 2 calls vs 1 call + 1 if statement.

Peter