关于元编程:Ruby元类疯狂

Ruby metaclass madness

我被困住了。我正试图动态地定义一个类方法,但我无法将我的思想集中在Ruby元类模型上。考虑以下类别:

1
2
3
4
5
6
7
8
9
10
11
12
13
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

显然,这两个方法都返回类的一个实例。但这两个例子不一样。他们也有不同的祖先:

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

在元类和类实例之间进行区别有什么意义?

我发现,我可以对元类执行send :define_method操作来动态定义一个方法,但是如果我试图将它发送到类实例,它将不起作用。至少我可以解决我的问题,但我仍然想知道为什么它是这样工作的。

更新2010年3月15日13:40

以下假设是否正确?

  • 如果我有一个实例方法调用self.instance并定义了一个方法,它将只影响该类的特定实例。
  • 如果我有一个实例方法调用self.class.instance_eval(与调用class_eval相同),并定义了一个方法,它将影响该特定类的所有实例,从而产生一个新的实例方法。
  • 如果我有一个调用实例eval并定义方法的类方法,它将为所有实例生成一个新的实例方法。
  • 如果我有一个类方法调用了meta/eigen类上的实例eval并定义了一个方法,它将导致一个类方法。

我想我开始明白了。如果类方法中的self指向本征类,那么它肯定会限制您的可能性。如果是这样,就不可能从类方法内部定义实例方法。对吗?


使用instance_eval时,动态定义singleton方法很简单:

1
2
3
4
5
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社区中称为singelton类或eigen类。该singleton类是可以向其中添加类(singleton)方法的类。

对于您试图使用class_instance方法定义的类实例,只不过是类本身,为了证明它,只需尝试向类Example添加一个实例方法,并通过检查该方法的存在性来检查您定义的class_instance方法是否返回类Example本身:

1
2
3
4
5
6
7
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的实例和类评估很有趣,不幸的是,class_evalinstance_eval混淆了,因为它们在某种程度上违背了它们的命名!

1
2
3
Use ClassName.instance_eval to define class methods.

Use ClassName.class_eval to define instance methods.

现在回答您的假设:

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.

对:

1
2
3
4
5
6
7
8
9
10
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.

在该上下文中,任何instance_eval都不会在类本身上定义单例方法(而不是实例方法):

1
2
3
4
5
6
7
8
9
10
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"]

为此,用上述的class_eval替换instance_eval

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.

不:

1
2
3
4
5
6
7
8
9
10
11
class Foo
  instance_eval do
    def assumption3()
      puts 'works'
    end
  end
end

Foo.instance_methods(false) #=> []

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

这将生成单例方法,而不是实例方法。为此,用上述的class_eval替换instance_eval

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.

嗯,不,这将是非常复杂的东西,因为它将把单例方法添加到单例类中,我认为它没有任何实际用途。


如果您在一个类上定义了一个方法,就可以在它的对象上调用它。它是一个实例方法。

1
2
3
4
5
6
7
8
9
class Example
end

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

Example.new.foo
#=>"foo"

如果在元类上定义方法,则可以在该类上调用它。这类似于其他语言中的类方法或静态方法的概念。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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中这样做:

1
2
3
4
5
6
7
8
9
10
11
12
str ="hello"
class << str
  def output
    puts self
  end
end

str
.output
#=>"hello"

"hi".output
# NoMethodError

如您所见,我们定义了一个方法,该方法只对字符串的一个实例可用。我们在上面定义这个方法的东西叫做元类。在方法查找链中,在搜索对象的类之前,首先访问元类。

如果我们用Class类型的对象替换String类型的对象,您可以想象为什么这意味着我们只在一个特定的类上定义一个方法,而不是在所有的类上。

当前上下文和self之间的差异很微妙,如果您感兴趣,可以阅读更多内容。