Безумие метакласса Ruby

Я застреваю. Я пытаюсь динамично определить метод класса, и я не могу перенести голову вокруг рубиновой модели метакласса. Рассмотрите следующий класс:

class Example

  def self.meta; (class << self; self; end); end

  def self.class_instance; self; end

end

Example.class_instance.class # => Class
Example.meta.class           # => Class

Example.class_instance  == Example      # => true
Example.class_instance  == Example.meta # => false

Очевидно, оба метода возвращают экземпляр Класса. Но эти два экземпляра не являются тем же. У них также есть различные предки:

Example.meta.ancestors            # => [Class, Module, Object, Kernel]
Example.class_instance.ancestors  # => [Example, Object, Kernel]

Какой смысл того, чтобы иметь значение между метаклассом и экземпляром класса?

Я выяснил, что я могу send :define_method к метаклассу для динамичного определения метода но если я пытаюсь отправить его в экземпляр класса, это не будет работать. По крайней мере, я мог решить свою проблему, но я все еще хочу понять, почему она прокладывает себе путь.

Обновление 15 марта 2010 13:40

Следующие корректные предположения.

  • Если у меня будет метод экземпляра, который называет сам instance_eval и определяет метод, то он будет только влиять на конкретный экземпляр того класса.
  • Если у меня есть метод экземпляра, который звонит сам class.instance_eval (который совпал бы с вызовом class_eval), и определяет метод, он будет влиять на все экземпляры того конкретного класса, приводящего к новому методу экземпляра.
  • Если у меня будет метод класса, который называет instance_eval и определяет метод, то он приведет к новому методу экземпляра для всех экземпляров.
  • Если у меня будет метод класса, который называет instance_eval на meta/eigen классе и определяет метод, то он приведет к методу класса.

Я думаю, что это начинает иметь смысл мне. Это, конечно, ограничило бы Ваши возможности, если сам в методе класса укажет на собственный класс. Раз так не было бы возможно определить метод экземпляра из метода класса. Это корректно?

14
задан Jon Seigel 16 April 2010 в 20:24
поделиться

2 ответа

Определить одноэлементный метод динамически просто, если вы используете instance_eval :

Example.instance_eval{ def square(n); n*n; end }
Example.square(2) #=> 4
# you can pass instance_eval a string as well.
Example.instance_eval "def multiply(x,y); x*y; end" 
Example.multiply(3,9) #=> 27

Что касается разницы выше, вы путаете две вещи :

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

Что касается экземпляра класса, который вы пытаетесь определить с помощью метода class_instance , это не что иное, как сам класс, чтобы доказать это, просто попробуйте добавить метод экземпляра к классу Пример и проверьте, возвращает ли определенный вами метод class_instance сам класс Example , проверив существование этого метода:

class Example
  def self.meta; (class << self; self; end); end
  def self.class_instance; self; end
  def hey; puts hey; end
end

Example.class_instance.instance_methods(false) #=> ['hey']

В любом случае, чтобы суммировать его для вас, когда вы хотите добавить методы класса, просто добавьте их в этот мета-класс. Поскольку метод class_instance бесполезен, просто удалите его.

В любом случае, я предлагаю вам прочитать этот пост , чтобы понять некоторые концепции системы отражения Ruby.

ОБНОВЛЕНИЕ

Предлагаю вам прочитать этот хороший пост: Развлечение с Ruby instance_eval и class_eval , К сожалению, class_eval и instance_eval являются сбивает с толку, потому что они каким-то образом работают против их именования!

Use ClassName.instance_eval to define class methods.

Use ClassName.class_eval to define instance methods.

Теперь отвечу на ваши предположения:

Если у меня есть метод экземпляра, который вызывает self.instance_eval и определяет метод , это повлияет только на конкретный экземпляр этот класс.

да:

class Foo
  def assumption1()
    self.instance_eval("def test_assumption_1; puts 'works'; end")
  end
end

f1 = Foo.new
f1.assumption1
f1.methods(false) #=> ["test_assumption_1"]
f2 = Foo.new.methods(false) #=> []

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

no instance_eval в этом контексте будет определять одноэлементные методы (не экземпляры) для самого класса:

class Foo
  def assumption2()
    self.class.instance_eval("def test_assumption_2; puts 'works'; end")
  end
end

f3 = Foo.new
f3.assumption2
f3.methods(false) #=> []
Foo.singleton_methods(false) #=> ["test_assumption_2"]

Чтобы это работало, замените instance_eval на class_eval ] выше.

Если у меня есть метод класса, который вызывает instance_eval и определяет метод, он приведет к созданию нового метода экземпляра для всех экземпляров.

Нет:

class Foo
  instance_eval do
    def assumption3()
      puts 'works'
    end
  end
end

Foo.instance_methods(false) #=> []

Foo.singleton_methods(false) #=> ["assumption_3"]

Это будет делать одноэлементные методы, а не методы экземпляра. Чтобы это работало, замените instance_eval на class_eval выше.

Если у меня есть метод класса, который вызывает instance_eval в мета / собственном классе и определяет метод, это приведет к методу класса.

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

11
ответ дан 1 December 2019 в 13:58
поделиться

Если вы определяете метод на классе, он может быть вызван на его объектах. Это метод экземпляра.

class Example
end

Example.send :define_method, :foo do
  puts "foo"
end

Example.new.foo
#=> "foo"

Если вы определяете метод на метаклассе, он может быть вызван на классе. Это похоже на концепцию метода класса или статического метода в других языках.

class Example
  def self.metaclass
    class << self
      self
    end
  end
end

Example.metaclass.send :define_method, :bar do
  puts "bar"
end

Example.bar
#=> "bar"

Причина существования метаклассов в том, что вы можете сделать это в Ruby:

str = "hello"
class << str
  def output
    puts self
  end
end

str.output
#=> "hello"

"hi".output
# NoMethodError

Как вы видите, мы определили метод, который доступен только для одного экземпляра String. То, для чего мы определили этот метод, называется метакласс. В цепочке поиска метода перед поиском класса объекта сначала обращаются к метаклассу.

Если мы заменим объект типа String на объект типа Class, вы можете представить, почему это означает, что мы определяем метод только для конкретного класса, а не для всех классов.

Различия между текущим контекстом и self очень тонкие, вы можете прочитать подробнее, если вам интересно.

5
ответ дан 1 December 2019 в 13:58
поделиться