Project

General

Profile

Actions

Feature #9779

open

Add Module#descendants

Feature #9779: Add Module#descendants

Added by shugo (Shugo Maeda) over 12 years ago. Updated 20 days ago.

Status:
Open
Assignee:
-
Target version:
-
[ruby-core:62193]

Description

Now classes have links to their subclasses, so how about to add Module#descendents?

class X
end

class Y < X
end

class Z < Y
end

p X.descendents    #=> [X, Y, Z]

module A
end

module B
  include A
end

module C
  include A
end

p A.descendents    #=> [A, C, B]

One of my own use cases is to implement AspectJ like pointcut which matches all subclasses of the given class.

Considerations:

  • Should the return value of Module#descendents include self?
  • Should the return value of Module#descendents include singleton classes?

I attach a patch, where the return value includes both self and singleton classes.


Files

0001-Add-Module-descendents.patch (3.75 KB) 0001-Add-Module-descendents.patch shugo (Shugo Maeda), 04/28/2014 07:00 AM

Related issues 1 (1 open0 closed)

Related to Ruby - Feature #14394: Class.descendantsAssignedko1 (Koichi Sasada)Actions

Updated by shugo (Shugo Maeda) over 12 years ago Actions #1 [ruby-core:62195]

s/descendents/descendants/

Updated by naruse (Yui NARUSE) over 8 years ago Actions #2

  • Target version deleted (2.2.0)

Updated by jeremyevans0 (Jeremy Evans) almost 5 years ago Actions #3

Updated by shugo (Shugo Maeda) 20 days ago Actions #4 [ruby-core:126047]

  • Subject changed from Add Module#descendents to Add Module#descendants

It seems that there's no progress since Class#descendants ([Feature #14394]) was reverted, so I've created a new pull request: https://github.com/ruby/ruby/pull/17807

I prefer Module#descendants because Class would automatically inherit Module#descendants but the converse does not hold.

I also believe Module#descendants is a more natural API than Class#descendants, because descendants should ideally be the dual of Module#ancestors; i.e., y ∈ x.descendants ⟺ x ∈ y.ancestors. Defining it at the Module level also answers the design question left open when [Feature #14394] was reverted (https://bugs.ruby-lang.org/issues/14394#note-33).

Assuming that Module#descendants is the dual of Module#ancestors, it should include self in its return value. However, I implemented Module#descendants not to include self, following ActiveSupport's Class#descendants and the reverted implementation of [Feature #14394]. Singleton classes are not included either, following the documented behavior of Class#subclasses. Note that [mod, *mod.descendants] can be used when self is needed.

The implementation reuses the existing subclass lists maintained for method cache invalidation, so no additional bookkeeping is introduced. Like Class#subclasses, the receiver does not hold strong references to its descendants, so the result may change after GC. Refinements are not included.

One use case of Module#descendants is Aspect Oriented Programming (illustrative, not a working example):

module Aspect
  def self.around(type, method_pattern, &advice)
    type.descendants.grep(Class).each do |klass|
      methods = klass.instance_methods(false).grep(method_pattern)
      next if methods.empty?

      wrapper = Module.new do
        methods.each do |name|
          define_method(name) do |*args, &blk|
            advice.call(self, name, args) { super(*args, &blk) }
          end
        end
      end
      klass.prepend(wrapper)
    end
  end
end

Aspect.around(Persistable, /\Asave/) do |obj, name, args, &proceed|
  logger.info("#{obj.class}##{name} start")
  proceed.call
ensure
  logger.info("#{obj.class}##{name} end")
end

A similar approach could also be used by instrumentation libraries such as OpenTelemetry and Datadog's dd-trace-rb.
Note that we have to use hooks like inherited or included to patch classes which will be loaded later.

Module#descendants is also useful for diagnostics, e.g., finding all classes a monkey patch is prepended to, or all models that include a specific Rails concern.

Actions

Also available in: PDF Atom