Por que o eigenclass não é equivalente a self.class, quando parece tão semelhante?

83

Perdi o memorando em algum lugar e espero que você me explique isso.

Por que o eigenclass de um objeto é diferente de self.class?

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

Minha linha de lógica que iguala o eigenclass com class.selfé bastante simples:

class << selfé uma maneira de declarar métodos de classe, em vez de métodos de instância. É um atalho para def Foo.bar.

Portanto, dentro da referência ao objeto de classe, o retorno selfdeve ser idêntico a self.class. Isso ocorre porque class << selfconfiguraria selfpara Foo.classpara definição de métodos / atributos de classe.

Estou apenas confuso? Ou isso é um truque furtivo de metaprogramação Ruby?

Robert K
fonte

Respostas:

122

class << selfé mais do que apenas uma forma de declarar métodos de classe (embora possa ser usado dessa forma). Provavelmente você viu algum uso como:

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

Isso funciona, e é equivalente a def Foo.a, mas a maneira como funciona é um pouco sutil. O segredo é que self, nesse contexto, se refere ao objeto Foo, cuja classe é uma subclasse única e anônima de Class. Essa subclasse é chamada Foode eigenclass . Assim, def acria um novo método chamado aem Fooeigenclass 's, acessível através da sintaxe de chamada de método normal: Foo.a.

Agora vamos ver um exemplo diferente:

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

Este exemplo é igual ao último, embora possa ser difícil dizer no início. frobé definido, não na Stringclasse, mas no eigenclass de str, uma subclasse anônima única de String. Então, strtem um frobmétodo, mas as instâncias de Stringem geral não. Também poderíamos ter métodos sobrescritos de String (muito útil em certos cenários de teste complicados).

Agora estamos equipados para entender seu exemplo original. FooO método de inicialização de dentro selfnão se refere à classe Foo, mas a alguma instância particular de Foo. Seu eigenclass é uma subclasse de Foo, mas não é Foo; não poderia ser, ou então o truque que vimos no segundo exemplo não funcionaria. Então, para continuar seu exemplo:

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.

Espero que isto ajude.

David Seiler
fonte
Então, cada instância é uma subclasse anônima da classe criada?
Robert K
21
A classe de cada instância é uma subclasse anônima da classe criada. A classe de f1 é uma subclasse anônima de Foo, a classe de Foo é uma subclasse anônima de Class.
David Seiler
6
boa resposta :) muitas pessoas não entendem isso tão claramente quanto você.
horseyguy
3
Como o eigenclass de f1 é diferente, conceitualmente, da instância real de f1. Se f1 é a única instância que terá acesso aos métodos de seu eigenclass, a distinção entre f1 e seu eigenclass não se quebra?
elju
1
@elju Sim, meio. A distinção realmente importante é entre "Foo" e "f1's eigenclass"; se você tem isso, provavelmente está bem.
David Seiler
46

A resposta mais simples: o eigenclass não pode ser instanciado.

class F
 def eigen
  class << self 
   self
  end
 end
end
F.new.eigen.new #=> TypeError: can't create instance of virtual class
b.vandgrift
fonte
você pode ter apenas 1 ponto neste site, mas gosto de você e do seu estilo.
horseyguy,
Concordo com corrimão; esta é uma ótima resposta
Christopher Scott de
3
Este é um comentário extremamente perspicaz e útil IFF que já lemos a resposta de @DavidSeiler acima.
Jazz de
O poder de Theo aqui está demonstrando a exceção que é gerada.
Nova Alexandria