Как получить константы, определенные классом Ruby Module через отражение?

Я пытался получить главу метапрограммирования Matz и Flanagan "Язык программирования Ruby" в моей голове. Однако я не мог понять результат из следующего фрагмента кода, о котором я мечтал:

p Module.constants.length           # => 88
$snapshot1 = Module.constants       
class A
  NAME=:abc

  $snapshot2 = Module.constants
  p $snapshot2.length               # => 90
  p $snapshot2 - $snapshot1         # => ["A", "NAME"]

end
p Module.constants.length           # => 89
p Module.constants - $snapshot1     # => ["A"]
p A.constants                       # => ["NAME"]

В книге указано, что метод класса constants возвращает список констант для класса (как вы можете видеть на выходе для A.constants). Я пытался получить список констант, определенных для класса модуля, когда я столкнулся с этим странным поведением.

A константы отображаются в Module.constants. Как получить список констант, определенных классом модуля?

docs state

Module.constants возвращает все константы, определенные в системе. включая имена всех классов и методов

Так как A наследует свою реализацию от Module.constants, как он ведет себя по-разному в базовом и производном типах?

p A.class               # => Class
p A.class.ancestors       # => [Class, Module, Object, Kernel]

Примечание. Если вы используете Ruby 1.9, constants вернет массив символов вместо строк.

Ответ 1

Хороший вопрос!

Ваше замешательство связано с тем, что метод класса Module.constants скрывает метод экземпляра Module#constants для Module.

В Ruby 1.9 это было устранено добавлением необязательного параметра:

# No argument: same class method as in 1.8:
Module.constants         # ==> All constants
# One argument: uses the instance method:
Module.constants(true)   # ==> Constants of Module (and included modules)
Module.constants(false)  # ==> Constants of Module (only).

В приведенном выше примере A.constants вызывает Module#constants (метод экземпляра), а Module.constants вызывает, ну, Module.constants.

В Ruby 1.9 вам нужно позвонить Module.constants(true).

В Ruby 1.8 можно вызвать метод экземпляра #constants на Module. Вам нужно получить метод экземпляра и связать его как метод класса (используя другое имя):

class << Module
  define_method :constants_of_module, Module.instance_method(:constants)
end

# Now use this new class method:
class Module
   COOL = 42
end
Module.constants.include?("COOL")  # ==> false, as you mention
Module.constants_of_module         # ==> ["COOL"], the result you want

Мне жаль, что я не смог полностью сохранить функциональность 1.9 до 1.8 для моего драгоценного камня backports, но я не могу придумать способ получить только константы модуля, исключая унаследованные, в Ruby 1.8.

Изменить: просто изменил официальную документацию, чтобы правильно отразить это...

Ответ 2

Мне пришлось вернуться в мою мыслящую пещеру некоторое время после ответа Марка. Переработаны дополнительные фрагменты кода, а затем еще несколько. Наконец, когда решение Ruby-метода показало смысл, оно записано в виде сообщения в блоге, чтобы я не забыл.

Обозначение: если A " является eigenclass A

Когда вызывается A.constants, разрешение метода (см. изображение в мой пост в блоге, чтобы иметь наглядную помощь), смотрит вверх в следующих местах:

  • MyClass", Object", BasicObject" (методы однопользовательского режима)
  • Class (методы экземпляра)
  • Module (методы экземпляра)
  • Object (методы экземпляра) и Kernel
  • BasicObject (методы экземпляра)

Ruby находит метод экземпляра Module#constants

Когда вызывается Module.constants, Ruby смотрит на

  • Module", Object", BasicObject" (методы Singleton)
  • Class (методы экземпляра)
  • Module (методы экземпляра)
  • Object (методы экземпляра) и Kernel
  • BasicObject (методы экземпляра)

на этот раз Ruby находит метод singleton/class в Module".constants, как сказал Марк.

Модуль определяет одноэлементный метод, который затеняет метод экземпляра. Метод singleton возвращает все известные константы, тогда как метод экземпляра возвращает константы, определенные в текущем классе и его предках.