¿Por qué ruby ​​crea 3 objetos después de crear una clase?

8

Estaba estudiando sobre la metaclase de Ruby. Yo leí esta respuesta en la que se describe muy bien lo que es metaclase. Se muestra allí cuando se crea una clase, creará dos objetos. Lo cual es comprensible. Uno para la clase en sí y otro para su metaclase. Pero cuando lo intento, veo que está creando tres 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

Estoy usando Ruby - 2.5.1.

¿Alguien puede ayudarme a entender esto?

Actualizar:

La referencia SO Post que añadí está utilizando ruby-1.9.1 o mayor, como el método count_objectspara ObjectSpacese introdujo en 1.9.1. Parece que el T_CLASSconteo siempre ha sido 3 (probado con ruby-1.9.3-p551).

Entonces, hasta ahora sigue siendo un misterio por qué esta respuesta . Ruby bajo un microscopio también dice que el recuento es 2.

Rafayet Monon
fuente
1
Cuando se ejecuta en la línea de comando, la diferencia es 2. Cuando se ejecuta con IRB la diferencia es 3. IRB parece estar haciendo algo por su cuenta. ¿Obtuviste tu resultado usando IRB? En cualquier caso, los ObjectSpacemétodos de ejecución ejecutados dentro de IRB (y Pry, tal vez) dan resultados distorsionados.
Cary Swoveland
1
@CarySwoveland: Yo también estaba pensando en esa dirección, excepto cuando lo ejecuto en la línea de comando, obtengo 3 también. La única diferencia es el total que obtengo. Cuando estoy en IRb, obtengo 1001 y 998 (y es bastante consistente entre las ejecuciones), en la línea de comando, obtengo significativamente menos, y cuando lo uso --disable-jit --disable-gems --disable-did_you_mean, obtengo aún menos, pero el conteo siempre es consistente en las ejecuciones y siempre difiere según 3. Estoy usando YARV 2.7.1 de Homebrew en macOS "Catalina" 10.15.4.
Jörg W Mittag
@ JörgWMittag y ...
Cary Swoveland
2
La última vez que lo comprobé, YARV siempre estaba ansiosamente creando clases singleton para módulos y clases como una optimización del rendimiento, bajo el supuesto de que los módulos y las clases casi siempre tendrán funciones de módulo y métodos de clase. Cleary, según los hallazgos de @ CarySwoveland, eso ya no es cierto. Realmente necesito actualizar mi conocimiento de los componentes internos de YARV. (He estado mucho más interesado en TruffleRuby y Rubinius en los últimos años y principalmente en ECMAScript los últimos tres). Todavía es un misterio de dónde viene esa tercera clase.
Jörg W Mittag
1
... Stefan y otros, hablo mal. Cuando ejecuto class Test; endla diferencia en conteo es 2; cuando ejecuto class Test; def self.t; end; endla diferencia es 3, aparentemente porque crear el método de clase crea Testla clase singleton. Sin embargo, si ejecuto ObjectClass.each_object(Class)antes y después, la diferencia en las matrices es [Test]en el primer caso y [Test, #<Class:Test>]en el segundo.
Cary Swoveland

Respuestas:

6

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

Crear una clase crea automáticamente una clase singleton (que no es accesible para el usuario). Hacer referencia a la clase singleton de una clase crea automáticamente una clase singleton de esa clase singleton. Esto es para mantener la coherencia de la estructura de herencia de las metaclases. De lo contrario, los métodos de clase no heredarían de la metaclase de la superclase, lo cual es necesario ya que los métodos de clase de la superclase deberían estar disponibles como métodos de clase de la subclase.

Modificando un poco el código de la pregunta:

$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

Obtengo los siguientes resultados:

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

Lo probé con Ruby 2.6 y 2.0 dentro y fuera de una consola (los números difieren pero la diferencia es la misma) y @SajibHassan con 1.9.3 (versión en la que count_objectsse introdujo el método ). Esto significa que la diferencia siempre ha sido 3 y que la primera clase singleton creada no es accesible para el usuario.

El libro Ruby Under a Microscope (escrito en 2012 después del lanzamiento de Ruby 2.1) también describe la creación de solo dos metaclases, que no coinciden con el resultado que obtenemos.

Tenga en cuenta que los métodos como Module#prepend(introducido en Ruby 2.0), que fue mencionado por @ JörgWMittag en los comentarios como la posible razón de esta clase adicional, use T_ICLASS. Verifique el commit en el que se introdujo el método para más detalles. Supongo que eso T_ICLASSsignifica clase interna y, en consecuencia, las clases internas no deberían ser visibles para el usuario (lo cual tiene sentido). Sin embargo, no estoy seguro de por qué algunos T_CLASSson accesibles para el usuario y otros no.

Ana María Martínez Gómez
fuente
2
He ejecutado esto en ruby ​​v1.9.3p551. pero el resultado es el mismo recuento 3. El autor debe usar> = 1.9.1 debido al método "count_objects" introducido en 1.9.1.
Sajib Hassan
Creo que esto puede ser un error, he informado: bugs.ruby-lang.org/issues/16788
Ana María Martínez Gómez
1
Hay una respuesta en su informe de error que dice que se espera ya que el otro objeto es para la clase singleton de la clase singleton. ¿Puedes editar tu respuesta con la referencia? No me queda mucho tiempo para otorgar la recompensa.
Rafayet Monon
¡Actualizado! Aunque todavía tengo curiosidad por qué se necesitan dos clases individuales.
Ana María Martínez Gómez
1
Sí, tengo curiosidad por eso también. Pero es para otra ocasión, supongo. Gracias por responder y llegar al fondo.
Rafayet Monon