1

下面描述了似乎是 Ruby 1.8(和 REE)中的错误,但已在 1.9 中修复。

我想知道:

  1. 为什么要调用或==触发String对象Fixnum调用?==other
  2. 为什么调用==String工作方式因other对象类而异?

现在代码:

class A
  def ==(other)
    puts "In A: calling == against #{other}"
    super
  end
end

class E < Exception
  def ==(other)
    puts "In E: calling == against #{other}"
    super
  end
end

示例 1

"foo" == A.new
=> false

这里没什么有趣的,继续前进。

示例 2

"foo" == E.new
In E: calling == against foo
=> false

==在类对象上调用String触发器。==otherException

示例 3

42 == A.new
In A: calling == against 42
=> false

在对象==上调用Fixnum触发器。==other

示例 4

42 == E.new
In E: calling == against 42
=> false

==在类对象上调用String触发器。==otherException

4

3 回答 3

2

似乎这仅适用于某些类型 - 在这种情况下是 Fixnums。我无法复制您的示例 #2(编辑:我做了,见底部),但示例 3 和 4 很容易复制。

因此,查看 numeric.c,我们看到 Ruby 调用equal?的是y(第二个参数),而不是x.

static VALUE
num_equal(x, y)
    VALUE x, y;
{
    if (x == y) return Qtrue;
    return rb_funcall(y, id_eq, 1, x);
}

看看这个例子:

1.8.7 :001 > class Foo
1.8.7 :002?>   def ==(other)
1.8.7 :003?>     puts "In foo"
1.8.7 :004?>     super
1.8.7 :005?>     end
1.8.7 :006?>   end
 => nil
1.8.7 :007 > 42 == Foo.new
In foo
 => false
1.8.7 :008 > Foo.new == 42
In foo
 => false
1.8.7 :009 > Foo.new == Foo.new
In foo
 => false
1.8.7 :010 > "" == Foo.new
 => false

在#7 中,我们将42(x) 与Foo.new(y) 进行比较。这y.==(x)将调用Foo#==.

在#8 中,我们将其反转;这里没有什么有趣的,我们只是调用Foo#==它,它的行为符合预期。

但是,在 #9 中,比较两个Foo结果实例只需要一次调用Foo#==. ==不会在参数上调用,仅在接收器上调用。

#10 根本不调用Foo#==

交换比较是 Ruby 数字类型实现的一个怪癖==,而不是 Ruby 相等运算符本身的特定属性。

编辑:字符串有同样的事情发生。

static VALUE
rb_str_equal(str1, str2)
    VALUE str1, str2;
{
    if (str1 == str2) return Qtrue;
    if (TYPE(str2) != T_STRING) {
      if (!rb_respond_to(str2, rb_intern("to_str"))) {
        return Qfalse;
      }
      return rb_equal(str2, str1);
    }
    if (RSTRING(str1)->len == RSTRING(str2)->len &&
      rb_str_cmp(str1, str2) == 0) {
      return Qtrue;
    }
    return Qfalse;
}

rb_equal首先使用第二个参数调用。它不调用的原因Foo#==Foo在我的示例中没有实现#to_str,所以它只是返回 false。Exception,但是, implements #to_str,因此它的子类在您的示例中被传递给rb_equal测试。

于 2013-06-07T23:52:42.233 回答
1

Ruby 是一种面向对象的语言。在面向对象的语言中,您向对象发送消息,然后这些对象以它们认为合适的方式响应这些消息。

这意味着消息的接收者,并且只有接收者(!)才能完全控制消息的含义。

但是,对于某些运算符,对对称性有一定的期望:例如,a == b期望与 相同b == a。但是在 OO 语言中这是不可能的!要么 要么a必须b 消息的接收者,因此在一种情况下a决定是否相等,在另一种情况下a决定。他们可能会做出不同的决定,然后对称性的期望​​就会被打破。bb

因此,在某些类中,平等实际上是这样实现的:

如果我知道你是谁,决定我们是否平等。但如果我不知道你是谁,我会让决定,因为也许知道是谁!

这是一个例子:如果你编写自己的Numeric类(比如一个Quaternion类),那么系统内置Fixnum类对 s 一无所知Quaternion。所以,当你问Fixnum 0它是否等于 时Quaternion (0, 0, 0, 0),它会响应false,即使那是错误的。

所以,相反,Fixnum将首先检查:我知道如何将自己与 a 进行比较Quaternion吗?不,我不知道,但也许 aQuaternion知道如何将自己与 a 进行比较Fixnum!毕竟在写类Quaternion的时候这个类还不存在Fixnum,所以Fixnum不能知道Quaternions。但是Quaternion写这门课的时候,可能作者考虑的太周到了,让Quaternions和Fixnums比较成为可能。

这就是为什么Fixnum#==扭转论点并再次尝试的原因。

String它是相同的,但有点复杂。在 Ruby 中,不是类型子类型子类化是不同的。Ruby 本身根本没有类型的概念!对象的类型是它的协议,即它理解的消息以及它如何响应它们。但是这个概念并没有记录在 Ruby 中(例如,与 Objective-C 不同,它确实有一个明确的协议概念)。

但是,在某些情况下,您打破 OO 封装,并了解特定类型,甚至更多:类型的特定表示。(注意:这违反了面向对象,但有时为了性能是必要的。)

如果 Ruby 需要一个对象属于特定类而不是仅仅响应特定协议,那么您将失去很多灵活性。例如,您必须使用 a String,即使您更愿意使用 a Rope。为了给你一些灵活性,Ruby 允许你传入一些不完全是 aString但等价于 one 并且可以通过to_str方法转换为 one 的东西。因此,与其他语言不同,其中A IS-A StringA的子类表示String,在 Ruby 中,关系由A IS-A String方法表示。Ato_str

这就是你在上面看到的。如果参数 toString#==不是 a String,那么String#==不知道如何处理它。但是,如果它是“类似字符串的”,即它实现了to_str,那么也许确实知道如何将自己与 a 进行比较String

请注意,平等很难做到正确。人们甚至无法就纯函数式语言的含义达成一致,这很简单!在 Ruby 中,还有两个额外的复杂性:可变状态和 OO。可变状态意味着片刻前相等的两个对象可能在片刻之后不再相等。或者他们应该是?还是不应该?OO意味着相等不能是对称的。

这就是为什么==各种核心和标准库类的实现不断改进的原因。这也是为什么你会时不时地看到奇怪的行为的原因。有时它可能只是试图使平等正确的产物,有时它可能只是一个错误。

顺便说一句:对于算术运算符,子类实际上有一个更正式的使用该方法Numeric的双重调度协议。coerce如果一个Numeric对象不知道如何处理另一个Numeric对象,它会向另一个对象询问这coerce两个对象到一个知道这一点的类型。例如,如果您尝试将 a 添加Quaternion到 a FixnumFixnum则将不知道该怎么做:

2 + Quaternion.new(1, 0, 0, 0)

然后将调用的+方法:Fixnum

a, b = other.coerce(self)

IOW:它将调用Quaternion#coerce相当于

Quaternion.new(1, 0, 0, 0).coerce(2)

Quaternion响应Array_[Quaternion.new(2, 0, 0, 0), Quaternion.new(1, 0, 0, 0)]

然后,Fixnum#+只需调用

a + b

现在可以使用,因为a它也是 aQuaternion并且知道如何添加两个Quaternions。

一个非常常见的实现coerce是简单地交换参数,即

def coerce(other)
  return other, self
end

这相当于您看到的行为Fixnum#==

再说一遍:这种调度很难做到正确,并且正在对coerce协议进行改进。

于 2013-06-08T00:57:04.690 回答
0

Ruby 有四个级别的对象等效性:

  1. #equal?– 相同的对象
  2. #eql?– 同一对象下的最高等价水平
  3. #==– “标准”平等
  4. #===, #=~, #hash, 等 – 松散等价,“同一组”等价

正念程序员为她的每个类配备了所有这些方法,除了#equal?,它们永远不应该被覆盖。回到您的问题的主题,许多内置的 Ruby 对象在这些方法的实现中都有其独特之处。Ruby 核心团队不断努力改进它们,因此您应该从 1.8 升级到 2.0,这修复了 1.8 的许多问题。关于method还有一点要提的#==是,对于表示有序集合元素的类,你不#==直接实现,而是提供#<=>三路比较的方法,并且include模块,它给你免费的方法,Comparable#==,、、,#<#>#sort等等。还有一件事要提到所有这些运算符和类似运算符的方法,请注意方法的存在#coerce,该方法目前正在 Ruby 2.0 中进行改进。

于 2013-06-08T00:39:16.050 回答