Ruby方法查找(与JavaScript比较)

时间:2015-02-06 05:19:50

标签: javascript ruby inheritance prototypal-inheritance

我想更好地理解Ruby中的对象如何具有在类和模块中定义的访问方法。具体来说,我想与JavaScript(我更熟悉)进行比较和对比。

在JavaScript 中,对象会在对象本身上查找方法,如果无法在那里找到它,它会在原型对象上查找方法。此过程将一直持续到Object.prototype

// JavaScript Example
var parent = {
  someMethod: function () {
    console.log( 'Inside Parent' );
  }
};

var child = Object.create( parent );
child.someMethod = function () {
  console.log( 'Inside Child' );
};

var obj1 = Object.create( child );
var obj2 = Object.create( child );

obj1.someMethod(); // 'Inside Child'
obj2.someMethod(); // 'Inside Child'

在JavaScript示例中,obj1obj2都没有对象本身的someMethod函数。需要注意的关键是:

  1. someMethod对象中child函数的一个副本,obj1obj2都委托给child对象。< / LI>
  2. 这意味着objobj2都没有对象本身的someMethod函数的副本。
  3. 如果child对象没有定义someMethod函数,则委派将继续到parent对象。
  4. 现在我想将其与Ruby中的类似示例

    进行对比
    # Ruby Example
    class Parent
      def some_method
        put 'Inside Parent'
      end
    end
    
    class Child < Parent
      def some_method
        puts 'Inside Child'
      end
    end
    
    obj1 = Child.new
    obj2 = Child.new
    
    obj1.some_method  # 'Inside Child'
    obj2.some_method  # 'Inside Child'
    

    以下是我的问题:

    1. Ruby代码每个中的obj1obj2是否拥有some_method方法的副本?或者它是否类似于JavaScript,其中两个对象都可以通过另一个对象访问some_method(在这种情况下,通过Child类)?
    2. 类似地,当在Ruby中考虑继承时,每个Ruby对象是否都具有同名的所有类和超类方法的副本
    3. 我的直觉告诉我,Ruby对象不要具有从其类,混合模块和超类继承的方法的单独副本。相反,我的直觉是Ruby处理类似于JavaScript的方法查找,其中对象检查对象本身是否具有方法,如果没有,它在对象的类,混合模块和超类中查找方法,直到查找到达BasicObject

3 个答案:

答案 0 :(得分:2)

让我们继续在IRB会话中使用您的示例,看看我们可能会学到什么:

> obj1.method(:some_method)
=> #<Method: Child#some_method>
> obj1.method(:some_method).source_location
=> ["(irb)", 8]
> obj2.method(:some_method)
=> #<Method: Child#some_method>
> obj2.method(:some_method).source_location
=> ["(irb)", 8]

好的,所以同一个类的两个对象具有相同的方法。我想知道这是否真的......

> obj1.instance_eval do
>   def some_method
>     puts 'what is going on here?'
>   end
> end
=> nil
> obj1.some_method
what is going on here?
=> nil
> obj2.some_method
Inside Child
=> nil
> obj1.method(:some_method)
=> #<Method: #<Child:0x2b9c128>.some_method>
> obj1.method(:some_method).source_location
=> ["(irb)", 19]

这很有趣。

詹姆斯·科格兰(James Coglan)有一篇很好的博客文章,提供了比https://blog.jcoglan.com/2013/05/08/how-ruby-method-dispatch-works/

更好的解释。

考虑这一点何时重要可能也很有趣。想想这个系统有多少是解释器的实现细节,并且可以在MRI,JRuby和Rubinius中以不同的方式处理,以及实际上需要保持一致的Ruby程序在所有这些系统中一致地执行。

答案 1 :(得分:1)

  
      
  1. Ruby代码每个中的obj1obj2是否拥有some_method方法的副本?或者它是否类似于JavaScript,其中两个对象都可以通过另一个对象访问some_method(在这种情况下,通过Child类)?
  2.   

你不知道。 Ruby语言规范简单地说&#34;如果你做这个 发生&#34;。但是,它没有规定使 发生的特定方式。每个Ruby实现都可以按照它认为合适的方式自由实现,只要结果与规范的结果相符,规范就不关心 那些结果是什么。

你无法说出来。如果实现保持适当的抽象,那么你就不可能告诉他们是如何做到的。这只是抽象的本质。 (实际上,它几乎就是抽象的定义。)

  
      
  1. 类似地,当在Ruby中考虑继承时,每个Ruby对象是否都具有同名的所有类和超类方法的副本
  2.   

与上述相同。

目前有一些很多的Ruby实现,过去在(完整性)的各个阶段都有更多。其中一些实现(编辑)他们自己的对象模型(例如MRI,YARV,Rubinius,MRuby,Topaz,tinyrb,RubyGoLightly),有些人坐在他们试图适合的现有对象模型之上(例如XRuby和JRuby on CLI上的Java,Ruby.NET和IronRuby,SmallTalk上的SmallRuby,smalltalk.rb,Alumina和MagLev,Objective-C / Cocoa上的MacRuby和RubyMotion,Parrot上的Cardinal,ActionScript / Flash上​​的Red Sun,SAP / ABAP上的BlueRuby ,ECMAScript上的HotRuby和Opal.rb)

谁能说所有这些实现都完全相同?

  

我的直觉告诉我,Ruby对象不要具有从其类,混合模块和超类继承的方法的单独副本。相反,我的直觉是Ruby处理类似于JavaScript的方法查找,其中对象检查对象本身是否具有方法,如果没有,它在对象的类,混合模块和超类中查找方法,直到查找到达BasicObject

尽管我上面写的, 是一个合理的假设,实际上,我所了解的实现(MRI,YARV,Rubinius,JRuby,IronRuby,MagLev,Topaz)是如何工作的

只要想想如果那么它意味着什么。 String类的每个实例都需要拥有自己的所有String 116方法的副本。想想典型的Ruby程序中有多少String个!

ruby -e 'p ObjectSpace.each_object(String).count'
# => 10013

即使在这个最简单的程序中,它们不会require任何库,并且只创建一个单独的字符串(用于将数字打印到屏幕上),已经 em>超过10000个字符串。其中每一个都有自己的100多个String方法的副本。这将是对记忆的巨大浪费。

这也是同步的噩梦! Ruby允许您随时使用monkeypatch方法。如果我重新定义String类中的方法怎么办? Ruby现在必须更新该方法的每个副本,即使是跨越不同的线程。

我实际上只计算了String中直接定义的公共方法。考虑到私有方法,方法的数量甚至更大。当然,还有继承:字符串不仅需要String中每个方法的副本,还需要ComparableObject,{{{{}}}中每个方法的副本。 1}}和Kernel。你能想象系统中的每个对象都有BasicObject的副本吗?

不,它在大多数Ruby实现中的工作方式都是这样的。对象具有标识,实例变量和类(在静态类型的伪Ruby中):

require

模块有方法字典,常量字典和类变量字典:

struct Object
  object_id: Id
  ivars: Dictionary<Symbol, *Object>
  class: *Class
end

一个类就像一个模块,但它也有一个超类:

struct Module
  methods: Dictionary<Symbol, *Method>
  constants: Dictionary<Symbol, *Object>
  cvars: Dictionary<Symbol, *Object>
end

当您在对象上调用方法时,Ruby将查找对象的struct Class methods: Dictionary<Symbol, *Method> constants: Dictionary<Symbol, *Object> cvars: Dictionary<Symbol, *Object> superclass: *Class end 指针并尝试在那里找到该方法。如果它没有,它将查看类的class指针等等,直到它到达没有超类的类。此时它实际上不会放弃,而是尝试在原始对象上调用superclass方法,将您尝试调用的方法的名称作为参数传递,但这只是一种常规方法也是调用,所以它遵循所有相同的规则(除非对method_missing的调用到达层次结构的顶部,它将不会再次尝试调用它,这将导致无限循环)。

哦,但我们忽略了一件事:单身方法!每个对象也需要有自己的方法字典。实际上,除了类之外,每个对象都有自己的私有单例类:

method_missing

因此,方法查找在单例类中首先启动 ,只有然后才会进入该类。

那么mixins呢?哦,对,每个模块和类还需要一个包含mixin的列表:

struct Object
  object_id: Id
  ivars: Dictionary<Symbol, *Object>
  class: *Class
  singleton_class: Class
end

现在,算法是:首先在单例类中查找,然后在类中查找,然后在超类中查看,然而,#34;看起来&#34;也就是说,在你查看方法字典之后,还要查看所包含的mixins的所有方法字典(以及所包含的mixin的包含的mixins,依此类推,递归)之前直到超类&#34;。

这听起来有多复杂吗?它是!这并不好。方法查找是面向对象系统中最常执行的算法,它需要简单且快速。因此,一些Ruby实现(例如MRI,YARV)所做的是解释解释器的内部概念,即什么&#34; class&#34;和&#34;超类&#34;从程序员对这些相同概念的看法来看。

一个对象不再具有单例类和类,它只有一个类:

struct Module
  methods: Dictionary<Symbol, *Method>
  constants: Dictionary<Symbol, *Object>
  cvars: Dictionary<Symbol, *Object>
  mixins: List<*Module>
end

struct Class
  methods: Dictionary<Symbol, *Method>
  constants: Dictionary<Symbol, *Object>
  cvars: Dictionary<Symbol, *Object>
  superclass: *Class
  mixins: List<*Module>
end

一个类不再包含一个包含mixins的列表,只是一个超类。然而,它可能是隐藏的。还要注意字典成为指针,你马上就会明白为什么:

struct Object
  object_id: Id
  ivars: Dictionary<Symbol, *Object>
  class: *Class
  singleton_class: Class
end

现在,对象的类指针将始终指向单例类,而单例类的超类指针将始终指向对象的实际类。如果将mixin struct Class methods: *Dictionary<Symbol, *Method> constants: *Dictionary<Symbol, *Object> cvars: *Dictionary<Symbol, *Object> superclass: *Class visible?: Bool end 包含到类M中,Ruby将创建一个新的不可见类C,它与mixin共享其方法,常量和cvar词典。这个mixin类将成为M′的超类,而C的旧超类将成为mixin类的超类:

C

实际上,它有点涉及,因为它还必须为M′ = Class.new( methods = M->methods constants = M->constants cvars = M->cvars superclass = C->superclass visible? = false ) C->superclass = *M' (和递归)中包含的mixin创建类,但最后,我们最终得到的是一个不错的线性方法查找路径,没有单步进入单例类并包含mixins。

现在,方法查找算法就是这样:

M

美观,干净,精益,快速。

作为权衡,找出一个对象的类或一个类的超类稍微困难一些,因为你不能简单地返回类或超类指针,你必须走链子直到你找到一个未隐藏的类。但是,您多久拨打一次def lookup(meth, obj) c = obj->class until res = c->methods[meth] c = c->superclass raise MethodNotFound, meth if c.nil? end res end Object#class?在调试之外你甚至可以打电话吗?

不幸的是,Class#superclass并不适合这张照片。并且精炼真的搞砸了,这就是许多Ruby实现甚至不实现它们的原因。

答案 2 :(得分:0)

更多值得思考的东西

> obj1.instance_eval do
>  def some_method
>    puts "Inside Instance"
>    super
>  end
> end 
=> :some_method
Inside Instance
Inside Child