Project

General

Profile

Actions

Feature #18273

closed

Class#subclasses

Added by byroot (Jean Boussier) about 1 month ago. Updated 12 days ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:105826]

Description

Ref: https://github.com/rails/rails/pull/43481

Something we forgot to mention in [Feature #14394], is either a parameter or another method to only get direct descendants.

Active Support has been offering Class.subclasses as:

  def subclasses
    descendants.select { |descendant| descendant.superclass == self }
  end

It seems a bit silly to grab all descendants and then restrict the list when Class#descendants had to do some recursion to get them all in the first place.

Proposal

We could either implement Class#subclasses directly, or accept a parameter in Class#descendants, e.g. descendants(immediate = false).

cc Eregon (Benoit Daloze)


Related issues

Related to Ruby master - Feature #14394: Class.descendantsClosedko1 (Koichi Sasada)Actions
Actions #1

Updated by byroot (Jean Boussier) about 1 month ago

  • Subject changed from Class.subclasses to Class#subclasses
Actions #2

Updated by byroot (Jean Boussier) about 1 month ago

Updated by Eregon (Benoit Daloze) about 1 month ago

+1 for Class#subclasses, I think it makes sense since we have Class#descendants.

Class#subclasses is then also the "complement" of Class#superclass,
just like Class#descendants is the complement of Class#ancestors.

Updated by mame (Yusuke Endoh) 20 days ago

I am afraid if the name "subclasses" is suitable. When we have three classes C, D that inherits from C, and E that inherits from D, we say "E is a subclass of C".

In fact, the rdoc of Module#< says

mod < other → true, false, or nil

Returns true if mod is a subclass of other.

and E < C returns true.

class C; end
class D < C; end
class E < D; end

p E < C #=> true

So, Class#subclasses sounds the same as Class#descendants.

If we take family line as an analogy, Class#children might be good. ko1 (Koichi Sasada) suggested Class#direct_subclasses.

Updated by byroot (Jean Boussier) 20 days ago

Fine by me, we'll have to go through a deprecation period in Rails but that's not a big deal.

What about it being a boolean parameter on descendants ? Was this option considered ?

Updated by sawa (Tsuyoshi Sawada) 20 days ago

I agree with mame, which brings in another question: Was the method name Class#superclass appropriate? Perhaps this method should also be renamed, say, to Class#parent.

An alternative is to allow an optional keyword direct (or immediate) on both Class#ancestors and Class#descendants.

Updated by mame (Yusuke Endoh) 20 days ago

byroot (Jean Boussier) wrote in #note-6:

What about it being a boolean parameter on descendants ? Was this option considered ?

descendants(immediate = false) looks reasonable to me. A keyword argument might be preferable to an optional argument.

sawa (Tsuyoshi Sawada) wrote in #note-7:

Was the method name Class#superclass appropriate?

I think we don't say "C is a superclass of E" in the same situation, but I'm unsure.

Updated by mame (Yusuke Endoh) 20 days ago

mame (Yusuke Endoh) wrote in #note-8:

sawa (Tsuyoshi Sawada) wrote in #note-7:

Was the method name Class#superclass appropriate?

I think we don't say "C is a superclass of E" in the same situation, but I'm unsure.

Ah, I misunderstood. I have no idea whether an alias Class#parent is needed.

Updated by Eregon (Benoit Daloze) 19 days ago

AFAIK superclass is always understand as the direct superclass.
And given we already have Class#descendants and Module#ancestors, I feel there is very little room for confusion for Class#subclasses (i.e., almost no confusion possible).
Of course we can document it clearly in that method's documentation it's only "classes which have C has the superclass", and link to Class#descendants.

IMHO Class#direct_subclasses is verbose and not helpful.
Rails already established that subclasses is a clear meaning for this.

Regarding a boolean argument for Class#descendants I'm neutral, I think that's less good and one definitely need to read the docs just to find the meaning of the boolean, plus they might not guess it even takes an argument.
Class#subclasses is clearer and is already an established name for this.

Updated by sawa (Tsuyoshi Sawada) 19 days ago

mame (Yusuke Endoh) wrote in #note-8:

I think we don't say "C is a superclass of E" in the same situation, but I'm unsure.

If there is any sense in understanding the word "superclass" to mean only the direct ancestor, that is because it is in singular form, not because the word per se implies directness. The singular form leads a programmer to infer that it must mean something unique, hence the direct ancestor, excluding the indirect ones. The word itself does not imply directness. In fact, there are some definitions out there that explicitly mention indirect ancestors to be included in "superclass":

The term superclass refers to a class's direct ancestor as well as all of its ascendant classes.

Object class is superclass of every class in Java.

The Object class, which is stored in the java.lang package, is the ultimate superclass of all Java classes (except for Object).

The Object class is the superclass of all other classes in Java and a part of the built-in java.lang package.

superclasses(ClassName) displays the names of all visible superclasses of the MATLAB® class with the name ClassName. Visible classes have a Hidden attribute value of false (the default).
...
superclasses(obj) displays the names of all visible superclasses of object obj, where obj is an instance of a MATLAB class. obj can be either a scalar object or an array of objects.

CommunityMember is the direct superclass of Employee, Student and Alumnus, and is an indirect superclass of all the other classes in the diagram.

When it comes to subclass, even the direct ones are not unique, which means that directness cannot be distinguished by referring to singularity, and it is this fact that is making it clearer that the word does not imply directness. The words subclasses and descendants mean the same thing. We cannot assign different meanings to them.

So my suggestion is that, as pointed out by mame, avoid the method name "subclasses", and as I previously suggested, deprecate "superclass" in favor of something else.

From compatibility and uniformity with other methods involving (in)directness such as Module#instance_methods, I now think it is a good idea to have the directness parameter for Class#ancestors and Class#descendants to be positional (and optional), with truthy value meaning "including indirect", and falsy meaning "only direct", default being truthy.

class C; end
class D < C; end
class F < C; end
class E < D; end

E.ancestors # => [E, D, C, Object, Kernel, BasicObject]
E.ancestors(false) # => D
C.descendants # => [C, D, F, E]
C.descendants(false) # => [D, F]

Updated by matz (Yukihiro Matsumoto) 18 days ago

Class#subclasses sounds OK. Accepted.

Matz.

Actions #13

Updated by byroot (Jean Boussier) 12 days ago

  • Status changed from Open to Closed

Applied in changeset git|c0c2b31a35e19a47b499b57807bc0a0f9325f6d3.


Add Class#subclasses

Implements [Feature #18273]

Returns an array containing the receiver's direct subclasses without
singleton classes.

Actions

Also available in: Atom PDF