Определить класс из собственного класса

В Ruby получить собственный класс класса Foo очень просто.

eigenclass = class << Foo; self; end
#=> #<Class:Foo>
eigenclass = Foo.singleton_class #2.1.0
#=> #<Class:Foo>

Меня интересует обратная операция: получение владельца собственного класса из самого собственного класса:

klass = eigenclass.owner
#=> Foo

Я не уверен, возможно ли это, учитывая, что собственный класс является анонимным подклассом Class, поэтому Foo нигде не появляется в иерархии наследования. Проверка списка методов собственного класса также не обнадеживает. eigenclass.name возвращает nil. Единственное, что дает мне надежду, что это возможно:

Class.new # normal anon class
#=> #<Class:0x007fbdc499a050>
Foo.singleton_class
#=> #<Class:Foo>

Очевидно, что метод to_s собственного класса знает что-то о владельце, даже если эта информация жестко запрограммирована при создании экземпляра собственного класса. Поэтому единственный метод, о котором я знаю, это какой-то хакерский Object.const_getting из этого, например

Object.const_get eigenclass.to_s[/^#\<Class\:(?<owner>.+)\>$/, :owner]
#=> Foo

person Chris Keele    schedule 01.02.2014    source источник
comment
Более кратко: учитывая значение "foo".singleton_class, как мы можем вернуться к "foo"?   -  person David Grayson    schedule 02.02.2014


Ответы (3)


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

Klass = Class.new
ObjectSpace.each_object(Klass.singleton_class).to_a  #=> [Klass]

Однако, поскольку одноэлементный класс класса наследуется от одноэлементного класса своего суперкласса, вы получите несколько результатов, если класс, который вы пытаетесь найти, имеет подклассы:

A = Class.new
B = Class.new(A)
B.singleton_class.ancestors.include?(A.singleton_class)  #=> true

candidates = ObjectSpace.each_object(A.singleton_class)
candidates.to_a  #=> [A, B]

К счастью, классы/модули можно сортировать по их месту в дереве наследования (тот же порядок ancestors дает). Поскольку мы знаем, что все результаты должны быть частью одного и того же дерева наследования, мы можем использовать max, чтобы получить правильный класс:

candidates.sort.last  #=> A
ObjectSpace.each_object(B.singleton_class).max  #=> B
person Andrew Marshall    schedule 01.02.2014
comment
Это не работает, если в дереве наследования есть ветвь: class A; end; class B < A; end; class C < A; end ObjectSpace.each_object(A.singleton_class).to_a #=> [C, B, A] ObjectSpace.each_object(A.singleton_class).sort #=> ArgumentError: comparison of Class with Class failed - person Chris Keele; 02.02.2014
comment
@ChrisKeele Хм, интересно. К сожалению, я не думаю, что есть действительно элегантный способ решить эту проблему. Конечно, он терпит неудачу, потому что не знает, ставить ли B перед C или наоборот. Учитывая это, я думаю, что ваш собственный ответ, вероятно, самый надежный, но без чрезмерной инженерии. - person Andrew Marshall; 02.02.2014
comment
Похоже на то. TIL, что модули можно сортировать, что очень удобно в других контекстах метапрограммирования. - person Chris Keele; 02.02.2014
comment
Мне интересно, как на самом деле реализована эта функция сравнения? Я имею в виду, что это только частичный порядок, поэтому сортировка на основе сравнения интуитивно не будет работать. Но Enumerable#sort основан на сравнении, верно? Это должно быть как-то хакерски раскрашивать поддеревья и лексикографически сравнивать кортежи цветов. Как-то странно. - person Niklas B.; 02.02.2014
comment
Согласно Module#<=> документации он возвращает nil, если два модуля не связаны или сравнение невозможно. Я предполагаю, что предки одинаковой глубины относятся к последней категории, и Enumerable#sort вызывает ошибку, если <=> не возвращает -1, 0 или 1. - person Chris Keele; 02.02.2014
comment
Эндрю, некоторые предпочитают max sort.last. :-) - person Cary Swoveland; 02.02.2014
comment
@ChrisKeele: Ах, так что это просто терпит неудачу, если элементы не образуют цепочку наследования :) Я рад это слышать, иначе это должна быть супер-хакерская функция сравнения: P - person Niklas B.; 02.02.2014
comment
@CarySwoveland Хороший вопрос, не знаю, почему это упрощение ускользнуло от меня, когда я писал это - я тоже предпочитаю его. Обновлено. - person Andrew Marshall; 03.02.2014

Уточнение ответа @BroiSatse рубиновым способом, не зависящим от реализации,

class A; end
class B < A; end
class C < A; end
eigenclass = A.singleton_class
ObjectSpace.each_object(eigenclass).find do |klass|
  klass.singleton_class == eigenclass
end
#=> A

Это также надежно при обработке ветвей в деревьях подклассов, единственная причина, по которой элегантный ответ @Andrew Marshall не работает.

person Chris Keele    schedule 01.02.2014

Используйте 1_:

e = class << 'foo'; self; end

ObjectSpace.each_object(e).first    #=> 'foo'

Чтобы получить объект изнутри собственного класса:

class << 'foo'
  puts ObjectSpace.each_object(self).first
end

#=> 'foo'  
person BroiSatse    schedule 01.02.2014
comment
Это довольно умно, но не на 100% надежно: class A; end; class B < A; end; ObjectSpace.each_object(A.singleton_class).first #=> B - person Chris Keele; 02.02.2014
comment
Я почти уверен, что это связано с тем, что большинство реализаций избегают создания экземпляров собственных классов до тех пор, пока они не понадобятся, поэтому, если A не вызывает свой собственный собственный класс в своем определении, он сначала вызывается при создании подкласса B. - person Chris Keele; 02.02.2014
comment
Какую версию ruby ​​вы используете, я все еще получаю рабочий код выше. - person BroiSatse; 02.02.2014
comment
Я на МРТ 2.1.0-р0 банкомат. - person Chris Keele; 02.02.2014