Trickery in the ancestors chain

  • Thread starter Paolo Nusco Perrotta
  • Start date
P

Paolo Nusco Perrotta

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

Brian Mitchell

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

Are you sure you entered the entire thing _in_order_? I had the same
sort of mistake when I was reading this post:

http://eigenclass.org/hiki.rb?cmd=view&p=class+hierarchy+introspection+evil.rb

It is a great read.

Brian.
 
A

ara.t.howard

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?

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
 
P

Phrogz

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]

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.
 
P

Pit Capitain

Phrogz said:
(...)
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
 
L

Luke Ivers

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]

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

Phrogz

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.
 
P

Paolo Nusco Perrotta

Catch-all reply:

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.
 
P

Pit Capitain

Phrogz said:
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
 
P

Pit

(...) 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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top