Included() vs extended()

Hi list,

Not sure if I should be asking this on the core list instead, but…
I’ve a
question about the included() and extended() hooks. Consider the
following:

A little helper from _why

class Object
def metaclass
class << self; self; end
end
end

A module with hooks

module Hooks
def self.included(base)
puts “including M”
end
def self.extended(base)
puts “extending using M”
end

def includes_hooks?
true
end
end

A couple classes

class Foo; end
class Bar; end

Prints “extending using M”

Foo.extend(Hooks)

Prints “including M”

Bar.metaclass.send(:include, Hooks)

Both return true

Foo.includes_hooks?
Bar.includes_hooks?

Check inheritance tree

Foo.metaclass.ancestors
#=> [Hooks, Class, Module, Object, Kernel]
Bar.metaclass.ancestors
#=> [Hooks, Class, Module, Object, Kernel]

So my question is: given that extend() is implemented (I think) using
metclass.include(), and that the end result of both is identical, how
come
Hooks.included is not called when using extend()? It seems reasonable
that
both the included() and extended() hooks should be called in this
situation,
though I can just as well see the argument against doing this. Anyone
care
to enlighten/persuade me as to why this behaviour exists?

So my question is: given that extend() is implemented (I think) using
metclass.include(), and that the end result of both is identical, how
come
Hooks.included is not called when using extend()? It seems reasonable
that
both the included() and extended() hooks should be called in this
situation,
though I can just as well see the argument against doing this. Anyone
care
to enlighten/persuade me as to why this behaviour exists?

In ruby everything is an object. So even class is an object.
Still we have extend and include which are not the same methods:

include => includes code into class.

extend => extends using module, appends class methods.

bar = Bar.new

bar - object of class Bar
bar.class - class of object bar
Bar - class Bar, object of class Class
Bar.metaclass - class of class Bar :slight_smile:

Bar.metaclass.send(:include, Hooks)

is called on class of class Bar. So you got the methods as instance
methods of class Bar.

My explaination may not have sense for you. Better read Why’s Poignant
Guide to Ruby - chapter 6 and/or Dr. Nic Williams PDF from euruko
conference http://www.euruko2008.org/pages/show/2-program.

Best Regards

Maciej

Hi –

On Tue, 2 Sep 2008, Maciej Tomaka wrote:

to enlighten/persuade me as to why this behaviour exists?

In ruby everything is an object. So even class is an object.
Still we have extend and include which are not the same methods:

include => includes code into class.

extend => extends using module, appends class methods.

include and extend both do the same thing: they add a module to the
method lookup path of an object or set of objects. So, for example:

class C
end

c = C.new
c.talk # looks in C, Object, Kernel
# no ‘talk’ method found: error

module M
def talk
puts “hi”
end
end

class C
include M
end

c.talk # looks in C, M …stops there because it finds ‘talk’

No code is added to C. When you use extend, you’re doing an “include”
operation on the object’s singleton class. So:

str = “hello”
str.extend(M)
str.talk # looks in its singleton class, then M…
# and finds ‘talk’

If you do, for example:

C.extend(M)

then you’re adding M to the lookup path of C. That means you can do:

C.talk

But note that it’s exactly the same pattern as before.

bar = Bar.new

bar - object of class Bar
bar.class - class of object bar
Bar - class Bar, object of class Class
Bar.metaclass - class of class Bar :slight_smile:

The class of class Bar is Class. The singleton class of Bar isn’t the
same as the class of Bar.

David

I have used bad words and only messed up stuff.

Thanks for fixing me.

Maciej

I belive you Mr. Black have a screencast where you explain exclude vs
include. Where can I purchase that video?

Hi,

I think you all kind of answered the wrong question. :wink:

James, I agree that it makes sense to regard “extend” as a special form
of “include”. But that does in no way mean that it’s actually
implemented like that. In fact, I’m pretty sure you won’t find anything
equivalent to

def extend mod
class << self
include mod
end
end

in the source code (written in C, of course).

Even on the semantic level I wouldn’t necessarily expect the include
hook to be called. Nobody said that an “extend” actually is an
“include”.

On Mon, Sep 1, 2008 at 4:58 PM, James C. [email protected]
wrote:

So my question is: given that extend() is implemented (I think) using
metclass.include(), and that the end result of both is identical, how come
Hooks.included is not called when using extend()? It seems reasonable that
both the included() and extended() hooks should be called in this situation,
though I can just as well see the argument against doing this. Anyone care
to enlighten/persuade me as to why this behaviour exists?

In 1.9.3 at least, Object#extend calls rb_mod_extend_object() which
just calls rb_extend_object() which is implemented as:

void
rb_extend_object(VALUE obj, VALUE module)
{
rb_include_module(rb_singleton_class(obj), module);
}

which bears out your hunch about the low-level implementation.

However, Object#extend and Module#include differ in that they call
different sets of hooks - #extend calls #extend_object and #extended
where #include calls #append_features and #included.

I guess #extend does not trigger a call to #included because it
enables you to distinguish the different use cases for a module (being
included into a class and extending a specific object).

You’ve shown that you can reproduce the effect of #extend by using
#include directly on a singleton class but by including a module
directly into a class’s singleton class you’re going ‘under the hood’
as it were (and note that you have to use #send(:include, Hooks) here
where you could have just used Bar.extend(Hooks) instead).

Given the different intended use cases for #include and #extend, it
does not strike me as anomalous that #extend does not trigger a call
to #included.

Regards,
Sean