Почему собственный класс не эквивалентен self.class, если он так похож?

84

Я где-то пропустил записку, и надеюсь, вы мне это объясните.

Почему собственный класс объекта отличается от self.class?

class Foo
  def initialize(symbol)
    eigenclass = class << self
      self
    end
    eigenclass.class_eval do
      attr_accessor symbol
    end
  end
end

Моя логика, приравнивающая собственный класс к class.self, довольно проста:

class << self- это способ объявления методов класса, а не методов экземпляра. Это ярлык на def Foo.bar.

Таким образом, внутри ссылки на объект класса возврат selfдолжен быть идентичен self.class. Это связано с тем, что class << selfбудет установлено selfзначение Foo.classдля определения методов / атрибутов класса.

Я просто запуталась? Или это хитрый трюк метапрограммирования Ruby?

Роберт К
источник

Ответы:

123

class << selfэто больше, чем просто способ объявления методов класса (хотя его можно использовать таким образом). Вероятно, вы видели такое использование, как:

class Foo
  class << self
    def a
      print "I could also have been defined as def Foo.a."
    end
  end
end

Это работает и эквивалентно def Foo.a, но способ, которым это работает, немного тонко. Секрет в том self, что в этом контексте относится к объекту Foo, класс которого является уникальным анонимным подклассом Class. Этот подкласс называется Foo«s eigenclass . Таким образом def aсоздается новый метод с именем aв собственном Fooклассе, доступный с помощью обычного синтаксиса вызова метода:Foo.a .

Теперь посмотрим на другой пример:

str = "abc"
other_str = "def"

class << str
  def frob
    return self + "d"
  end
end

print str.frob # => "abcd"
print other_str.frob # => raises an exception, 'frob' is not defined on other_str

Этот пример такой же, как и предыдущий, хотя сначала может быть трудно сказать. frobопределен не в Stringклассе, а в собственном классе strуникального анонимного подкласса String. У strнего есть frobметод, но у экземпляров Stringв целом его нет. Мы также могли бы иметь переопределенные методы String (очень полезные в некоторых сложных сценариях тестирования).

Теперь мы готовы разобраться в вашем первоначальном примере. Внутри Fooинициализации метод «ы, selfотносится не к классу Foo, но в какой - то конкретной инстанции в Foo. Его собственный класс является подклассом Foo, но это не так Foo; этого не могло быть, иначе трюк, который мы видели во втором примере, не сработал. Итак, чтобы продолжить ваш пример:

f1 = Foo.new(:weasels)
f2 = Foo.new(:monkeys)

f1.weasels = 4 # Fine
f2.monkeys = 5 # Also ok
print(f1.monkeys) # Doesn't work, f1 doesn't have a 'monkeys' method.

Надеюсь это поможет.

Дэвид Зайлер
источник
Итак, каждый экземпляр является анонимным подклассом созданного класса?
Роберт К.
21
Класс каждого экземпляра является анонимным подклассом созданного класса. Класс f1 является анонимным подклассом Foo, класс Foo является анонимным подклассом Class.
Дэвид Сейлер,
6
хороший ответ :) многие люди не понимают этого так ясно, как вы.
Horseyguy
3
Чем собственный класс f1 концептуально отличается от фактического экземпляра f1. Если f1 - единственный экземпляр, который когда-либо будет иметь доступ к методам своего собственного класса, разве не нарушается различие между f1 и его собственным классом?
elju
1
@elju Да, вроде как. Действительно важное различие - между «Foo» и «собственным классом f1»; если у вас есть это, вы, вероятно, в порядке.
Дэвид Зайлер
48

Самый простой ответ: экземпляр собственного класса не может быть создан.

class F
 def eigen
  class << self 
   self
  end
 end
end
F.new.eigen.new #=> TypeError: can't create instance of virtual class
b.vandgrift
источник
у вас может быть только 1 балл на этом сайте, но вы мне нравитесь и ваш стиль.
Horseyguy
Согласен с перилами; это отличный ответ
Кристофер Скотт
3
Это чрезвычайно проницательный и полезный комментарий. IFF уже прочитал ответ @ DavidSeiler выше.
Джаз
Тео Пауэр здесь демонстрирует возникшее исключение.
Новая Александрия