【问题标题】:Ruby metaclass madnessRuby 元类疯狂
【发布时间】:2024-01-14 15:22:01
【问题描述】:

我被困住了。我正在尝试动态定义一个类方法,但我无法理解 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

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

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

区分元类和类实例有什么意义?

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

2010 年 3 月 15 日 13:40 更新

以下假设是否正确。

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

我认为它开始对我有意义。如果类方法中的 self 指向 eigen 类,那肯定会限制您的可能性。如果是这样,就不可能从类方法内部定义实例方法。对吗?

【问题讨论】:

    标签: ruby metaprogramming dynamic metaclass class-method


    【解决方案1】:

    使用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 社区中称为 singelton 类eigen 类。该单例类是您可以添加类(单例)方法的类。

    至于您尝试使用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这个方法没用,去掉就好。

    无论如何我建议你阅读this post 以掌握一些Ruby反射系统的概念。

    更新

    我建议你阅读这篇好文章:Fun with Ruby's instance_eval and class_eval, 不幸的是,class_evalinstance_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) 并定义了一个方法 将影响所有实例 特定的类导致一个新的 实例方法。

    没有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 并定义将导致的方法 一个类方法。

    不,那会做这么复杂的东西,因为它会给单例类添加单例方法,我认为这不会有任何实际用途。

    【讨论】:

    • 有关为什么instance_eval 中的def 定义类方法的更多信息,请参阅这篇文章yugui.jp/articles/846
    • 到目前为止非常感谢。我更新我的问题。你介意看一下吗?
    • 非常感谢您的详细回答。我可能需要一些时间才能完全理解 Ruby 类模型的概念。
    【解决方案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 类型的对象替换为Class 类型的对象,您可以想象为什么这意味着我们只在特定 类上定义一个方法,而不是在所有类上类。

    当前上下文和self 之间的差异是微妙的,如果您有兴趣,可以read more

    【讨论】: