Por que ruby ​​cria 3 objetos após a criação de uma classe?

8

Eu estava estudando sobre a metaclasse de Ruby. I ler esta resposta onde é bem descreveu o que metaclass é. É mostrado lá quando uma classe é criada, ela cria dois objetos. O que é compreensível. Um para a classe em si e outro para sua metaclasse. Mas quando estou tentando, vejo que está criando três objetos.

puts "Before Class Creation object count - #{ObjectSpace.count_objects[:T_CLASS]}"
class Test
  def self.foo # test_singleton
    p 'Printed from method #foo'
  end

  def bar # test
    p 'Printed from method #bar'
  end
end
puts "After Class Creation object count - #{ObjectSpace.count_objects[:T_CLASS]}"

###############

Before Class Creation object count - 949
After Class Creation object count - 952

Eu estou usando Ruby - 2.5.1.

Alguém pode me ajudar a entender esse?

Atualizar:

A referência de forma a deixar que é adicionado usando rubi-1.9.1 ou superior, tal como o método count_objectspara ObjectSpacefoi introduzido em 1.9.1. Parece que a T_CLASScontagem sempre foi sempre 3 (tentada com ruby-1.9.3-p551).

Então, até agora ainda é um mistério o porquê dessa resposta . Ruby sob um microscópio também diz que a contagem é 2.

Rafayet Monon
fonte
1
Quando executado na linha de comando, a diferença é 2. Quando executado com IRB, a diferença é 3. O IRB parece estar fazendo algo por conta própria. Você obteve seu resultado usando o IRB? De qualquer forma, os ObjectSpacemétodos de execução executados no IRB (e Pry, talvez) fornecem resultados distorcidos.
Cary Swoveland 04/04
1
@CarySwoveland: Eu estava pensando nessa direção, também, exceto quando eu executá-lo na linha de comando, recebo 3 também. A única diferença é o total que recebo. Quando no IRb, recebo 1001 e 998 (e é bastante consistente entre as execuções), na linha de comando, obtenho significativamente menos e, quando uso --disable-jit --disable-gems --disable-did_you_mean, obtenho menos ainda, mas a contagem é sempre consistente entre as execuções e sempre difere por 3. Estou usando o YARV 2.7.1 da Homebrew no macOS "Catalina" 10.15.4.
Jörg W Mittag
@ JörgWMittag and ...
Cary Swoveland
2
Da última vez que verifiquei, o YARV estava sempre criando classes singleton para módulos e classes como uma otimização de desempenho, sob a suposição de que módulos e classes quase sempre terão funções e métodos de classe. Cleary, de acordo com as descobertas de @ CarySwoveland, isso não é mais verdade. Eu realmente preciso atualizar meu conhecimento sobre os YARV internos. (Estive muito mais interessado em TruffleRuby e Rubinius nos últimos dois anos e principalmente no ECMAScript nos últimos três.) Ainda é um mistério de onde vem essa terceira classe.
Jörg W Mittag
1
... Stefan e outros, eu errei. Quando corro, class Test; enda diferença na contagem é 2; quando executo class Test; def self.t; end; enda diferença é 3, aparentemente porque a criação do método class cria Testa classe singleton da. No entanto, se eu executar ObjectClass.each_object(Class)antes e depois da diferença nas matrizes, é [Test]no primeiro caso e [Test, #<Class:Test>]no segundo.
Cary Swoveland 04/04

Respostas:

6

De https://bugs.ruby-lang.org/issues/16788 :

Criar uma classe cria automaticamente uma classe singleton (que não é acessível ao usuário). A referência à classe singleton de uma classe cria automaticamente uma classe singleton dessa classe singleton. Isso é para manter a consistência da estrutura de herança das metaclasses. Caso contrário, os métodos de classe não herdariam da metaclasse da superclasse, o que é necessário, pois os métodos de classe da superclasse devem estar disponíveis como métodos de classe da subclasse.

Modificando um pouco o código da pergunta:

$old_classes = []
def print_objects
  new_classes = []
  ObjectSpace.each_object(Class){|x| new_classes << x}
  puts "New classes: #{new_classes - $old_classes}" unless $old_classes.empty?
  puts "Counts: #{ ObjectSpace.count_objects[:T_CLASS] }"
  $old_classes = new_classes
end

print_objects

class Test
end
puts 'Test class created'
print_objects

class Test
  def self.foo
  end 
end
puts 'Test singleton class referenced'
print_objects

Eu recebo os seguintes resultados:

Counts: 690
Test class created
New classes: [Test]
Counts: 692
Test singleton class referenced
New classes: [#<Class:Test>]
Counts: 693

Eu tentei com o Ruby 2.6 e 2.0 dentro e fora de um console (os números diferem, mas a diferença é a mesma) e o @SajibHassan com 1.9.3 (versão na qual o método count_objectsfoi introduzido). Isso significa que a diferença sempre foi 3 e que a primeira classe singleton criada não está acessível para o usuário.

O livro Ruby Under a Microscope (escrito em 2012 após o lançamento do Ruby 2.1) também descreve a criação de apenas duas metaclasses, o que não corresponde ao resultado obtido.

Observe que métodos como Module#prepend(introduzidos no Ruby 2.0), mencionados por @ JörgWMittag nos comentários como o possível motivo para essa classe extra, usam T_ICLASS. Verifique a confirmação na qual o método foi introduzido para obter detalhes. Eu acho que T_ICLASSsignifica classe interna e, consequentemente, classes internas não devem ser visíveis para o usuário (o que faz sentido). Não sei ao certo por que alguns T_CLASSsão acessíveis ao usuário e outros não.

Ana María Martínez Gómez
fonte
2
Eu executei isso no ruby ​​v1.9.3p551. mas o resultado é a mesma contagem 3. O autor deve usar> = 1.9.1 devido ao método "count_objects" introduzido na 1.9.1.
Sajib Hassan 14/04
Eu acho que isso pode ser um bug, eu relatei: bugs.ruby-lang.org/issues/16788
Ana María Martínez Gómez
1
Há uma resposta no seu relatório de bug que diz que é esperado, pois o outro objeto é para a classe singleton da classe singleton. Você pode editar sua resposta com a referência. Não tenho muito tempo para conceder a recompensa.
Rafayet Monon 16/04
Atualizada! Embora eu ainda esteja curioso por que duas classes singleton são necessárias.
Ana María Martínez Gómez
1
Sim, eu também estou curioso sobre isso. Mas é para outra hora, eu acho. Obrigado por responder e chegar ao fundo.
Rafayet Monon 16/04