Сумасшествие в металле Ruby

Я застрял. Я пытаюсь динамически определить метод класса, и я не могу обернуть голову вокруг модели метакласса 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

Правильны ли следующие допущения.

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

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

Ответы

Ответ 1

Определение метода singleton динамически просто, когда вы используете 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

Что касается вышеизложенного, вы вводите в заблуждение 2 вещи:

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

Что касается экземпляра класса, который вы пытаетесь определить с помощью метода class_instance, это не что иное, как сам класс, чтобы доказать его, просто попробуйте добавить метод экземпляра к классу Example и проверьте, что 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.

UPDATE

Я предлагаю вам прочитать этот хороший пост: Fun с 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"]

Это сделает методы singleton, а не методы экземпляра. Для этого замените instance_eval на class_eval выше.

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

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

Ответ 2

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

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 тонкие, вы можете прочитать больше, если вам интересно.