Project

General

Profile

Bug #11878

Comparison of prepended modules

Added by sawa (Tsuyoshi Sawada) over 4 years ago. Updated 7 months ago.

Status:
Rejected
Priority:
Normal
Target version:
-
ruby -v:
2.3.0p0 (2015-12-25 revision 53290) [x86_64-linux]
[ruby-core:72493]

Description

Including module B to class/module A gives the following results (as expected):

module A; end
module B; end
A.include B
A < B # => true
B < A # => false
A <=> B # => -1

And prepending module C to A gives the following results:

module C; end
A.prepend C
A < C # => true
C < A # => nil
A <=> C # => -1

It looks like including and prepending almost do not make difference with respect to module comparison, i.e., A < B and A < C are the same, and A <=> B and A <=> C are the same. However, then, the difference between B < A and C < A stands out unexplained. I suppose this is a bug. If C < A were to return false, then it would be at least consistent.

However, if that was what was intended, then at least to me, it is strange. In that case, I would like to make this a feature request. I would rather expect:

A < C # => false
C < A # => true
A <=> C # => 1

Updated by marcandre (Marc-Andre Lafortune) over 4 years ago

  • Assignee set to nobu (Nobuyoshi Nakada)

Indeed.

#2

Updated by nobu (Nobuyoshi Nakada) over 4 years ago

  • Status changed from Open to Closed

Applied in changeset r53380.


object.c: fix prepend cmp

  • object.c (rb_class_inherited_p): search the corresponding ancestor to prepended module from prepending class itself. [ruby-core:72493] [Bug #11878]

Updated by nobu (Nobuyoshi Nakada) over 4 years ago

  • Description updated (diff)
  • Backport changed from 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN to 2.0.0: REQUIRED, 2.1: REQUIRED, 2.2: REQUIRED, 2.3: REQUIRED

Updated by sawa (Tsuyoshi Sawada) over 4 years ago

As far as I see the revision r53380 by Nakada san, it looks like my feature proposal part was rejected. Is this the case?

Updated by nobu (Nobuyoshi Nakada) over 4 years ago

Making prepending class an ancestor of prepended module?
It feels strange a little, to me.

Updated by sawa (Tsuyoshi Sawada) over 4 years ago

I thought that the ordering relation among modules/classes represents the method call priority. A < B means that method look-up first looks in A, then B; smaller module/class has higher priority. If so, since a module prepended to a class has higher priority than the class, the module should be smaller (<) than the class. Is my interpretation wrong?

Updated by naruse (Yui NARUSE) over 4 years ago

  • Status changed from Closed to Assigned
  • Assignee changed from nobu (Nobuyoshi Nakada) to matz (Yukihiro Matsumoto)

Updated by mame (Yusuke Endoh) 8 months ago

  • Status changed from Assigned to Rejected

The current behavior is consistent. The rdoc of Module#< says:

call-seq:
  mod < other   ->  true, false, or nil

Returns true if <i>mod</i> is a subclass of <i>other</i>.

Note that subclass is not directly related to the method lookup order. Consider the following example:

module M; end
class C; prepend M; end

C.new.is_a?(M) #=> true

This means C is a subclass of M. So, C < M should return true. Note that, in terms of method lookup, M has a higher priority than 'C'.

Updated by Eregon (Benoit Daloze) 8 months ago

  • Status changed from Rejected to Open

At the very least it's inconsistent with the order of Module#ancestors:

module M; end
class C; prepend M; end

> C.ancestors
=> [M, C, Object, JSON::Ext::Generator::GeneratorMethods::Object, PP::ObjectMixin, Kernel, BasicObject]
> C < Object
=> true
> M < C
=> false
> C < M
=> true

I think no user expects that the "subclass relation" is different than the order of ancestors, isn't it? (and why would it need to be?)
Also, the documentation says nothing about modules or this ad-hoc order which nothing else seems to use.

I think this is a bug and I'd like matz's ruling.

Updated by Eregon (Benoit Daloze) 8 months ago

Re obj.is_a?(mod) we can consider it as obj.singleton_class.ancestors.include?(mod) or even obj.class.ancestors.include?(mod) in this case.
That's consistent with ancestors.

Updated by mame (Yusuke Endoh) 8 months ago

I think no user expects that the "subclass relation" is different than the order of ancestors, isn't it?

Regardless whether users know or not, they are actually different. Consider:

module M; end
class C; prepend M; end
class D; include M; end

If M is a subclass of C, D is a subclass of C. Both A.prepend(B) and A.include(B) make A to be a subclass of B.

Note that the order of Module#ancestors is not specified; the rdoc says nothing.

(IMO, Module#prepend is a very bad thing that makes the object system complicated.)

Updated by matz (Yukihiro Matsumoto) 7 months ago

  • Status changed from Open to Rejected

For the code like below:

module A; end

module I
  include A
end

p A < I #=> false
p A > I #=> true

module P
  prepend A
end

# current: same as include
p A < P #=> false
p A > P #=> true

A > P does not mean P is a subclass of A, but P includes the method sets defined in A. So the current behavior should not be changed.

Matz.

Also available in: Atom PDF