Рубиновое безумие метакласса - PullRequest
14 голосов
/ 15 марта 2010

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

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

Ответы [ 2 ]

11 голосов
/ 15 марта 2010

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

Предлагаю вам прочитать этот хороший пост: Приколы с 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 в классе meta / eigen и определяет метод, который приведет к метод класса.

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

5 голосов
/ 15 марта 2010

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

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

...