Trickery in the ancestors chain

Try this:

module M
def hello; p ‘M’; end
end

class C
def hello; p ‘C’; end
end

class D < C; end

class C; include M; end
class D; include M; end
D.ancestors -> [D, C, M, Object, Kernel]
D.new.hello -> “C”

Now restart from a clean slate and change the last few lines:

class D; include M; end
class C; include M; end
D.ancestors -> [D, M, C, M, Object, Kernel]
D.new.hello -> “M”

Ruby silently prevents you from including a module that has been
included by an ancestor. Why?

On 1/24/07, Paolo Nusco P. [email protected] wrote:

class D; include M; end
class C; include M; end
D.ancestors → [D, M, C, M, Object, Kernel]
D.new.hello → “M”

Ruby silently prevents you from including a module that has been
included by an ancestor. Why?

Stop Press
I can reproduce that behavior but only on my cygwin ruby 1.8.4 I cannot
reproduce it in irb, irb seems to produce the correct behaviour (the
first
one for both versions).

As soon as I’ll have time I’ll fire up tests on my Ubuntu with 1.8.5 and
1.9
.

Robert

On Wed, 24 Jan 2007, Paolo Nusco P. wrote:

class D < C; end
class C; include M; end
D.ancestors -> [D, M, C, M, Object, Kernel]
D.new.hello -> “M”

Ruby silently prevents you from including a module that has been
included by an ancestor. Why?

why do you say that? you first tell D to include M, which ruby does.
then
you tell C to include M, which ruby does. that’s why M is in the
ancestors
list twice. am i missing something. try working with this from the
console
so we’re on the same page:

harp:~ > cat a.rb

take one

 module M
   def hello; 'M'; end
 end
 class C
   def hello; 'C'; end
 end
 class D < C; end
 class C; include M; end
 class D; include M; end
 p D.ancestors                      #=> [D, C, M, Object, Kernel]
 p D.new.hello                      #=> "C"

take two

 Object.instance_eval{
   remove_const 'C'
   remove_const 'D'
 }
 class C
   def hello; 'C'; end
 end
 class D < C; end
 class D; include M; end
 p D.ancestors                      #=> [D, M, C, Object, Kernel]
 class C; include M; end
 p D.ancestors                      #=> [D, M, C, M, Object, Kernel]
 p D.new.hello                      #=> "M"

harp:~ > ruby a.rb
[D, C, M, Object, Kernel]
“C”
[D, M, C, Object, Kernel]
[D, M, C, M, Object, Kernel]
“M”

this makes perfect sense to me attm - you’re picking up ‘hello’ from M
and it’s
first up the method lookup chain. but maybe i’m not understanding?

regards.

-a

Phrogz schrieb:

(…)
Seems like a bug, like it should only prevent the M inclusion if it’s
already included directly in the class.

But it works as documented. Why do you want to change it?

Regards,
Pit

On Thu, 25 Jan 2007 01:55:07 +0900
“Phrogz” [email protected] wrote:

 end

lower level."

Seems like a bug, like it should only prevent the M inclusion if it’s
already included directly in the class.

Yeah, but isn’t this a dangerous road to go down? You will end up with
people including a module to get some functionality and re-over-writing
inherited methods that were over-writing instance methods in the
original module. I guess we should be allowed to do that, but it’s
probably going to cause more headaches to do it that way than it’s going
to solve… if you really need to use the original definition of a
module’s instance method that’s been over-written already by a
superclass, you have to question why you’re using that superclass at
all… if you don’t want its functionality, but the functionality of
something that it’s inheriting, maybe you should consider some other
sort of object hierarchy?

On Jan 24, 10:04 am, Pit C. [email protected] wrote:

Phrogz schrieb:

(…)
Seems like a bug, like it should only prevent the M inclusion if it’s
already included directly in the class.

But it works as documented. Why do you want to change it?

I have two, different responses:

  1. OK, you’re right: if it’s documented that way, it’s probably not a
    bug. But just because it’s designed, implemented, and
    documented…doesn’t mean that it’s necessarily the correct behavior.
    I’d be interested in hearing arguments for why it makes sense to
    prevent a situation that can be achieved via a different call order?

  2. I personally don’t want to change it; I’ve never run into this
    situation, and it seems very unlikely to me that I would ever have a
    situation where this would be needed. When would you find a class that
    includes a module, that has a method named the same as that class, and
    need to shadow the class you’re inheriting from? I was just trying to
    explain to Ara the ‘bug’ that the OP was describing.

Catch-all reply:

On Jan 24, 7:03 pm, “Phrogz” [email protected] wrote:

But it works as documented. Why do you want to change it?I have two, different responses:

  1. OK, you’re right

Yes, it’s a feature - I found the C implementation in file class.c,
lines 396 and following (for Ruby 1.8.5). Ruby skips the inclusion of M
in D when D already includes M by way of an ancestor (or itself). This
normally prevents me from adding the same module to the ancestors chain
twice. But I can work around this by including the module in D first,
and later in the ancestor.

I see that most people think this is how it should work. I guess I’m
easily surprised, but I wasn’t expecting the order of inclusions into a
hierarchy to make a difference. So, I wonder: why does Ruby go to the
trouble of preventing this in the first place? If it does, shouldn’t it
do it all the time, not sometimes? If including a module twice is an
error, shouldn’t Ruby raise an exception? I don’t have a strong opinion
on this, but it feels strange.

On 1/24/07, [email protected] [email protected] wrote:

def hello; p ‘C’; end
Now restart from a clean slate and change the last few lines:

 class D; include M; end
   def hello; 'C'; end

[D, C, M, Object, Kernel]
“C”
[D, M, C, Object, Kernel]
[D, M, C, M, Object, Kernel]
“M”

this makes perfect sense to me attm - you’re picking up ‘hello’ from M and
it’s
first up the method lookup chain. but maybe i’m not understanding?

Very good explanation Ara, makes perfect sense.
I missed this point, but I was interrupted by my boss and wanted to post
my
partial findings…
… which was stupid.
I just checked it in irb again and now when I did it right it works
perfectly.
Sorry for the noise.

Remark to Brian, thx for the link I know and appreciate all work of
Mauricio.

Cheers
Robert

regards.

Phrogz schrieb:

On Jan 24, 10:04 am, Pit C. [email protected] wrote:

But it works as documented. Why do you want to change it?

I have two, different responses:

  1. OK, you’re right: if it’s documented that way, it’s probably not a
    bug. But just because it’s designed, implemented, and
    documented…doesn’t mean that it’s necessarily the correct behavior.

Absolutely. That’s why I asked for reasons to change it.

I’d be interested in hearing arguments for why it makes sense to
prevent a situation that can be achieved via a different call order?

Yes, maybe the bug is here:

D.ancestors # => [D, M, C, M, Object, Kernel]

But from what I know of the Ruby interpreter, this would be hard to fix.

Regards,
Pit

On Jan 24, 8:48 am, [email protected] wrote:

 class D < C; end
 class C; include M; end
 class D; include M; end
 p D.ancestors                      #=> [D, C, M, Object, Kernel]

I think the issue here is that essential no-op:

module M; end
class C; end
class D < C; end

class C; include M; end
x = D.ancestors
class D; include M; end
y = D.ancestors

p x==y
#=> true

The issue is that Ruby says “Hey, M is already in your ancestor
list…I’m going to prevent you from inserting it into the chain at a
lower level.”

Seems like a bug, like it should only prevent the M inclusion if it’s
already included directly in the class.

On 24 Jan., 20:17, “Paolo Nusco P.”
[email protected] wrote:

(…) So, I wonder: why does Ruby go to the
trouble of preventing this in the first place?

Paolo, the Ruby interpreter does a lot to prevent including a module
twice. It’s much more than the lines you’ve found. This is also the
source of a well known problem with Module inclusion, for which there’s
no solution yet. All this could be prevented without this behaviour. So
it seems there must be a very good reason to have it, but I don’t know
it right now. Perhaps someone else here does?

If it does, shouldn’t it
do it all the time, not sometimes? If including a module twice is an
error, shouldn’t Ruby raise an exception? I don’t have a strong opinion
on this, but it feels strange.

Yes, I think so too. But as I’ve written in another post, I think this
would be hard to fix.

Regards,
Pit