【问题标题】:why are metaclasses created in ruby?为什么在 ruby​​ 中创建元类?
【发布时间】:2013-07-09 15:22:44
【问题描述】:

我正在尝试理解 Ruby 对象模型。我知道实例方法保存在类中而不是类的对象中,因为它消除了冗余。我读到,每当创建一个类时,也会为新创建的类创建一个元类。元类存储类方法。即类的单例方法位于元类中。例如

class MyClass
  def hi
    'hi object'
  end

  def self.bye
    'bye singleton method'
  end
end

对于上面的 MyClass,也创建了一个元类(比如#MyClass)。现在方法“hi”是一个实例级方法,可以在 MyClass 的所有对象上调用。 'bye' 方法是 MyClass 的单例方法,它驻留在 #MyClass 中。 'hi' 保存在 MyClass 而不是 MyClass 的所有对象中的原因(我认为是这样)是因为它避免了冗余。但是我们不能有多个名为 MyClass 的类。 那么为什么不将“再见”存储在 MyClass 而不是 #MyClass,因为我们不能拥有多个 MyClass。我完全不知道为什么会这样,我只是想了解背后的原因。

-----更新----

元类存储类信息,如单例方法和其他东西。但是既然一个类是一个单例对象(它是类 Class 的一个实例并且是它的类型)那么为什么不将所有信息保存在类本身而不是元​​类中。

【问题讨论】:

  • 是什么让您认为创建了元类?
  • ObjectSpace.count_objects[:T_CLASS] 在声明新类时将计数增加 2。
  • 如何确定额外的一个是元类?您是查看源代码还是只是假设额外的对象是元类?
  • 我刚刚读到的关于元类的有趣文章 - ruby-metaprogramming.rubylearning.com/html/…
  • @texasbruce 我正在阅读“显微镜下的红宝石”。 Pat 解释说元类是创建的,但不幸的是他没有解释它是创建的。此外,“元编程 Ruby”也这么说。你必须阅读它,因为它很有趣。

标签: ruby


【解决方案1】:

只是为了超级清楚。

这是一个解释问题的快速 ruby​​ 脚本:

#!/usr/bin/env ruby
puts ObjectSpace.count_objects[:T_CLASS] #>> 471
class X
  def self.foo
  end
  def bar
  end
end
puts ObjectSpace.count_objects[:T_CLASS] #>> 473

这就是“ObjectSpace.count_objects[:T_CLASS] 将计数增加 2”的 OP 含义。让我们将额外的类称为 X 的单例类,因为这似乎是 Ruby 内部所称的。

irb> X
=> X
irb> X.singleton_class
=> <Class: X>

注意#foo 方法是X.singleton_class 的实例方法,而不是X

irb> X.instance_methods(false)
=> [:baz]
irb> X.singleton_class.instance_methods(false)
=> [:foo]

那么为什么:foo 存储在X.singleton_class 而不是XX 不是只有一个吗?

我认为主要原因是一致性。考虑以下关于普通实例对象的更简单的场景。

car = Car.new
def car.go_forth_and_conquer
end

正如@mikej 精彩解释的那样,这个新方法存储在汽车的单例类中。

irb> car.singleton_class.instance_methods(false)
=> [:go_forth_and_conquer]

类是对象

现在,类也是对象。每个类都是Class 的一个实例。因此,当定义了一个类(例如,X)时,ruby 实际上是在创建一个Class 的实例,然后向该实例添加方法(类似于我们上面对car 所做的。)例如,这里是创建新类的另一种方法

Car = Class.new do
  def go_forth_and_conquer
    puts "vroom"
  end
end
Car.new.go_forth_and_conquer

因此,重用代码并以相同的方式执行会更容易(即,将foo 保留在X.singleton_class 中。)这可能需要更少的努力并且会导致更少的意外,所以没有人需要编写代码以区别于其他实例来处理 Class 实例。

可能没关系

您可能会想,如果 Ruby 没有用于 Class 实例的单例类,那么可能会节省一些内存。但是,在我看来,bar 实际存储的位置是我们可能不应该指望的实现细节。只要行为一致,Rubinius、MRI 和 JRuby 都可以不同地存储方法和实例。据我们所知,可能存在一个合理的 Ruby 实现,它不会急切地为类对象创建单例类,原因与您概述的完全相同,只要整体行为符合 ruby​​ 规范。 (例如,在第一次调用 #singleton_class 方法之前,实际的单例类不存在。)

【讨论】:

  • 我知道这个答案很旧,但我使用的是 ruby​​ 2.5.1,它正在为我创建三个对象。有什么理由吗?
【解决方案2】:

这不是您问题的完全答案,但它可能有用。考虑两件事可能会有所帮助:

  • 当您想到meta 前缀在其他场景中的使用方式时,元类并不是一个很好的名称。 eigenclass 您将在其他文档中看到使用的可能是一个更好的名称,意思是“对象自己的类”
  • 不仅仅是类有特征类,每个对象都有

eigenclass 用于存储特定于特定对象的方法。例如我们可以为单个 String 对象添加一个方法:

my_string = 'Example'
def my_string.example_method
  puts "Just an example"
end

此方法只能在my_string 上调用,而不能在任何其他 String 对象上调用。我们可以看到它存储在my_string的特征类中:

eigenclass = class << my_string; self; end # get hold of my_string's eigenclass
eigenclass.instance_methods(false) # => [:example_method]

记住类是对象,在这种情况下,特定类的特定方法应该存储在该类的特征类中是有意义的。


更新:实际上,特征类有一个特征类。如果我们将 eigenclass 作为方法添加到 Object 中,我们可以更容易地看到这一点:

class Object 
  def eigenclass 
    class << self
      self
    end 
  end 
end

然后我们可以这样做:

irb(main):049:0> my_string.eigenclass
=> #<Class:#<String:0x269ec98>>
irb(main):050:0> my_string.eigenclass.eigenclass
=> #<Class:#<Class:#<String:0x269ec98>>>
irb(main):051:0> my_string.eigenclass.eigenclass.eigenclass # etc!
=> #<Class:#<Class:#<Class:#<String:0x269ec98>>>>

虽然这似乎造成了无限的倒退,但这是可以避免的,因为 Ruby 只在需要时创建特征类。我认为“元类”这个名称确实是您感到困惑的部分原因,因为您期望“元类”包含某种实际上没有的信息。

【讨论】:

  • 我同意可以声明对象的方法。我们已经习惯了类作为模板和对象作为实际实例的概念,以至于我们忘记了在 ruby​​ 中,类也是对象。将方法保存在“类”中而不是对象中的好方法,它增加了为类的每个对象及其纯重复显式添加它的开销。
  • 但我对为每个类创建元类/特征类只是为了存储类信息的方法更加困惑。那么元类信息存储在哪里呢?元类有元类吗?我知道它不存在,但我只是对这种方法感到好奇。
【解决方案3】:

原因归结为self 是什么。对于任何给定的方法,self 是定义该方法的对象的一个​​实例。

在实例方法中,存储在MyClass 上,self 将是您调用#hiMyClass 的实例。在类方法中,self 将是元类的实例(即类本身)。对元类进行操作意味着self 的概念没有改变,即使在对本身只是其元类的单例实例的类进行操作时也是如此。

【讨论】:

    【解决方案4】:

    根据The Ruby Programming Language,类方法实际上是与类同名的类实例上的单例方法。

    Class Foo
      def self.bar
        "Am a class method"
      end
    end
    

    这里的方法self.bar 可以描述为Class Foo 类型的实例Foo 上的单例方法

    #the following code is just to explain on what actually are class methods called
    Foo.bar #=> "Am a class method" 
    #the call is done on an instance of class Foo which got ref name Foo
    

    我们可以继续在Foo 上添加更多类/单例/元类方法

    class<<Foo
      def self.another_bar
        "Am another class method"
      end
    end
    
    Foo.another_bar #=>"Am another class method"
    

    更正式的单例方法被定义为匿名特征类/元类的实例方法。

    虽然在概念上是错误的,但我们可以假设类在这种情况下是对象,以便更好地掌握。

    这个概念将真正的面向对象引入到语言的各个层面。 Objective-C 以类似的方式实现类方法。

    在 Obj-C 中,元类作为类退出,其中包含有关 meta 的类的信息。继承的原则也适用于元类,超类是它的类的超类的元类,并且向上爬,所以直到它到达基础对象,谁的元类就是元类本身。可以在here 上阅读更多内容。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-06-02
      • 1970-01-01
      • 2014-09-23
      • 1970-01-01
      • 2021-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多