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 String由A的子类表示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 Fixnum,Fixnum则将不知道该怎么做:
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协议进行改进。