Классы классов, одноэлементные классы

Я играю с метапрограммированием в рубине, и у меня есть вопрос. У меня есть класс:

class Klass
  class << self
    @x = "yeah"
  end
end
b = Klass.new
a = class << Klass; self; end
a.instance_eval "@x"           #=> yeah
Klass.instance_eval "@x"       #=> nil

Почему? В переменной a у меня есть одноэлементный класс, верно? И Klass.instance_eval exec в контексте одноэлементного класса:

Klass.instance_eval "def yeah; puts 10; end"
Klass.yeah                     #=> 10

Кроме того, Klass в интерпретаторе указывает на контекст класса, да? А a указывает на контекст одноэлементного класса? А что означает a.class_eval и a.instance_eval? Я:

a.instance_eval "def pops; puts 0; end"
a.class_eval "def popsx; puts 1; end"
a.pops                         #=> 0
a.popsx                        # FAIL
Klass.pops                     # FAIL
Klass.popsx                    #=> 1
b.pops; b.popsx                # DOUBLE FAIL

и я не понимаю этого. Благодарю!

Ответ 1

Во-первых, в то время как кажется, что eigentclass используется некоторыми людьми, одноклассник является более распространенным термином. Класс Singleton содержит поведение объекта для объекта в Ruby. Вы не можете создавать другие экземпляры этого класса, кроме исходного объекта, к которому принадлежит этот класс singleton.

Говоря об определении методов внутри разных типов eval в этой статье, вводится хорошее правило для методов, определенных в instance_eval и class_eval:

Use ClassName.instance_eval to define class methods.
Use ClassName.class_eval to define instance methods.

Это в значительной степени описывает ситуацию.

Была огромная запись о классах, которые являются экземплярами класса Class, их одноэлементных классов, которые являются подклассами класса Class и некоторыми другими сумасшедшими (не так сильно связанными с проблемой). Но поскольку ваш вопрос можно легко применить к обычным объектам и их классам (и это значительно упрощает объяснение), я решил удалить все (хотя вы все еще можете увидеть этот материал в истории изменений ответа).

Посмотрите на обычный класс и экземпляр этого класса и посмотрите, как все это работает:

class A; end
a = A.new

Определения методов внутри разных типов eval:

# define instance method inside class context
A.class_eval { def bar; 'bar'; end }
puts a.bar     # => bar
puts A.new.bar # => bar

# class_eval is equivalent to re-opening the class
class A
  def bar2; 'bar2'; end
end
puts a.bar2     # => bar2
puts A.new.bar2 # => bar2

Определение объектно-ориентированных методов:

# define object-specific method in the context of object itself
a.instance_eval { def foo; 'foo'; end }
puts a.foo # => foo

# method definition inside instance_eval is equivalent to this
def a.foo2; 'foo2'; end
puts a.foo2 # => foo2

# no foo method here
# puts A.new.foo # => undefined method `foo' for #<A:0x8b35b20>

Теперь рассмотрим одноэлементный класс объекта a:

# singleton class of a is subclass of A
p (class << a; self; end).ancestors
# => [A, Object, Kernel, BasicObject]

# define instance method inside a singleton class context
class << a
  def foobar; 'foobar'; end;
end
puts a.foobar # => foobar

# as expected foobar is not available for other instances of class A
# because it instance method of a singleton class and a is the only
# instance of that class
# puts A.new.foobar # => undefined method `foobar' for #<A:0x8b35b20>

# same for equivalent class_eval version
(class << a; self; end).class_eval do
  def foobar2; 'foobar2'; end;
end
puts a.foobar2 # => foobar2

# no foobar2 here as well
# puts A.new.foobar2 # => undefined method `foobar2' for #<A:0x8b35b20>

Теперь рассмотрим переменные экземпляра:

# define instance variable for object a
a.instance_eval { @x = 1 }

# we can access that @x using same instance_eval
puts a.instance_eval { @x } # => 1
# or via convenient instance_variable_get method
puts a.instance_variable_get(:@x) # => 1

И теперь к экземпляру переменных внутри class_eval:

# class_eval is instance method of Module class
# so it not available for object a
# a.class_eval { } # => undefined method `class_eval' for #<A:0x8fbaa74>

# instance variable definition works the same inside
# class_eval and instance_eval
A.instance_eval { @y = 1 }
A.class_eval    { @z = 1 }

# both variables belong to A class itself
p A.instance_variables # => [:@y, :@z]

# instance variables can be accessed in both ways as well
puts A.instance_eval { @y } # => 1
puts A.class_eval    { @z } # => 1

# no instance_variables here
p A.new.instance_variables # => []

Теперь, если вы замените класс a классом Class и объектом a на объект Klass (который в этой конкретной ситуации является не чем иным, как экземпляром класса Class), я надеюсь, что вы получите объяснение на ваши вопросы. Если у вас все еще есть вопросы, не стесняйтесь спрашивать.

Ответ 2

Трудно полностью ответить на ваш вопрос (для подробного объяснения модели класса Ruby посмотрите отличную презентацию Дейва Томаса), тем не менее:

С class_eval вы фактически определяете методы экземпляра - это как если бы вы были внутри тела класса. Например:

class Klass
end

Klass.class_eval do
  def instance_method
    puts 'instance method'
  end
end

obj = Klass.new
obj.instance_method  # instance method

С instance_eval вы фактически определяете методы класса - это как если бы вы были внутри тела класса singleton (eigenclass) данного объекта (nota bene, что классы тоже объекты в Ruby). Например:

Klass.instance_eval do
  def class_method
    puts 'class method'
  end
end

Klass.class_method  # class method

И в вашем случае:

Klass.instance_eval "@x" не работает, потому что @x не является частью Klass, это часть класса Singleton класса Klass:

class Klass
  class << self
    @x = "yeah"
  end

  puts @x
end

# prints nothing

a.instance_eval "@x" отлично работает, потому что вы оцениваете "@x" в контексте класса singleton класса a, который связан с классом singleton класса Klass, в котором вы указали переменную экземпляра @x. В этом примере показано, как одноэлементные классы могут быть взаимосвязаны:

class Foo
end

f = class << Foo; self; end
g = class << Foo; self; end

f.instance_eval "def yeah; puts 'yeah'; end"

g.yeah  # yeah

g.instance_eval "def ghee; puts 'ghee'; end"

f.ghee  # ghee

Klass.instance_eval "def yeah; puts 10; end" определяет метод "нормального" класса. Поэтому Klass.yeah работает нормально (см. Klass.class_method в предыдущем примере).

a.instance_eval "def pops; puts 0; end" определяет метод класса в классе a singleton. Следовательно, a.pops на самом деле означает вызов метода класса pops (опять же, как будто он вызывает Klass.class_method).

a.popsx не работает, потому что вам сначала нужно создать экземпляр a, чтобы иметь возможность вызвать popsx на нем (но невозможно создать новый экземпляр одноэлементного класса).

Klass.pops не работает, потому что не существует метода pops, определенного в классе Singleton класса Klass (pops определяется в классе a singleton).

Klass.popsx работает, потому что с помощью a.class_eval "def popsx; puts 1; end" вы определили метод экземпляра popsx, который затем вызывается для объекта Klass. Это, в некотором роде, похоже на этот пример:

class Other
end

o = Other.new

Other.class_eval "def yeah; puts 'yeah'; end"

o.yeah  # yeah

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