开发者

Ruby metaclass madness

开发者 https://www.devze.com 2022-12-23 12:16 出处:网络
I\'m stuck. I\'m trying to dynamically define a class method and I can\'t wrap my head around the ruby metaclass model. Consider the following class:

I'm stuck. I'm trying to dynamically define a class method and I can't wrap my head around the ruby metaclass model. Consider the following class:

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

Obviously both methods return an instance of Class. But these two instances are not the same. They also have different ancestors:

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

What's the开发者_JAVA百科 point in making a difference between the metaclass and the class instance?

I figured out, that I can send :define_method to the metaclass to dynamically define a method, but if I try to send it to the class instance it won't work. At least I could solve my problem, but I still want to understand why it is working this way.

Update Mar 15, 2010 13:40

Are the following assumptions correct.

  • If I have an instance method which calls self.instance_eval and defines a method, it will only affect the particular instance of that class.
  • If I have an instance method which calls self.class.instance_eval (which would be the same as calling class_eval) and defines a method it will affect all instances of that particular class resulting in a new instance method.
  • If I have a class method which calls instance_eval and defines a method it will result in a new instance method for all instances.
  • If I have a class method which calls instance_eval on the meta/eigen class and defines a method it will result in a class method.

I think it starts to make sense to me. It would certainly limit your possibilities if self inside an class method would point to the eigen class. If so it would not be possible to define an instance method from inside a class method. Is that correct?


Defining a singleton method dynamically is simple when you use 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

As for the difference above, you are confusing 2 things:

The meta class defined by you, is what called in Ruby community as singelton class or eigen class. That singleton class is the class that you can add class(singleton) methods to.

As for the class instance you are trying to define using the class_instance method, is nothing but the class itself, to prove it, just try adding an instance method to the class Example and check if the class_instance method defined by you returns the class Example itself by checking the existence of that method:

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']

Anyway to sum it for you, when you want to add class methods, just add them to that meta class. As for the class_instance method is useless, just remove it.

Anyway I suggest you read this post to grasp some concepts of Ruby reflection system.

UPDATE

I suggest you read this nice post: Fun with Ruby's instance_eval and class_eval, Unfortunately class_eval and instance_eval are confusing because they somehow work against their naming!

Use ClassName.instance_eval to define class methods.

Use ClassName.class_eval to define instance methods.

Now answering your assumptions:

If I have an instance method which calls self.instance_eval and defines a method, it will only affect the particular instance of that class.

yes:

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) #=> []

If I have an instance method which calls self.class.instance_eval (which would be the same as calling class_eval) and defines a method it will affect all instances of that particular class resulting in a new instance method.

no instance_eval in that context will define singleton methods(not instance ones) on the class itself:

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"]

For that to work replace instance_eval with class_eval above.

If I have a class method which calls instance_eval and defines a method it will result in a new instance method for all instances.

Nope:

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

Foo.instance_methods(false) #=> []

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

That will make singleton methods, not instance methods. For that to work replace instance_eval with class_eval above.

If I have a class method which calls instance_eval on the meta/eigen class and defines a method it will result in a class method.

well no, that will make so sophisticated stuff, as it will add singleton method to the singleton class, I don't think that will have any practical use.


If you define a method on a class, it can be invoked on its objects. It is an instance method.

class Example
end

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

Example.new.foo
#=> "foo"

If you define a method on a metaclass, it can be invoked on the class. This is similar to the concept of a class method or static method in other languages.

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

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

Example.bar
#=> "bar"

The reason that metaclasses exist is because you can do this in Ruby:

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

str.output
#=> "hello"

"hi".output
# NoMethodError

As you can see, we defined a method that is only available to one instance of a String. The thing that we defined this method on is called the metaclass. In the method lookup chain, the metaclass is accessed first before searching the object's class.

If we replace the object of type String with an object of type Class, you can imagine why this means we're only defining a method on a specific class, not on all classes.

The differences between the current context and self are subtle, you can read more if you're interested.

0

精彩评论

暂无评论...
验证码 换一张
取 消