我在irb玩耍,注意到一个人做不到
5 * "Hello"
。
错误
字符串无法强制进入Fixnum
但"Hello"*5
按预期提供了"HelloHelloHelloHelloHello"
。
这是什么原因?我一直在浏览文档,但找不到这种行为的确切原因。这是红宝石设计师决定的吗?
答案 0 :(得分:3)
这是因为运算符也是方法(Cary已经在评论中列出了例外情况,我不知道)。
例如
array << 4 == array.<<4
array[2] == array.[](2)
array[2] ='x' == array.[] =(2,'x')
在您的示例中:
5 * "Hello"
=&gt; 5.*("Hello")
同时
"hello" *5 => 5.*("hello")
integer
无法使用string
参数
如果你曾经在python尝试5*hello
和hello*5
,那么两者都有效。非常有趣的是,ruby有这个功能是诚实的。
答案 1 :(得分:3)
基本上,你要问&#34;为什么乘法不是可交换的&#34;?这有两个可能的答案。或者更确切地说是两层的答案。
OO的基本原则是一切都是由于一个对象将消息发送到另一个对象而该对象响应该消息而发生的。这个&#34;消息传递&#34;隐喻非常重要,因为它解释了OO中的很多东西。例如,如果您向某人发送消息,您可以观察到的是他们的回答。你不知道,也不知道他们做了什么来做出回应。他们本可以发出预先录制的响应(引用实例变量)。他们本可以努力构建响应(执行方法)。他们本可以将信息传递给其他人(代表团)。或者,他们只是不理解您发送的邮件(NoMethodError
)。
请注意,这意味着邮件的接收者完全可以控制。接收器可以以任何方式响应它的意愿。这使得发送的消息本身不可交换。通过foo
作为参数发送消息a
至<{1}} 基本与向b
发送消息foo
不同b
作为一个参数。在一种情况下,a
只有a
决定如何回复邮件,而在另一种情况下,a
只有b
。
使这种可交换需要b
和a
之间的明确合作。他们必须就共同协议达成一致并遵守该协议。
在Ruby中,二元运算符只是向左操作数的消息发送。因此,它只是左操作数决定要做什么。
所以,在
b
消息'Hello' * 5
将使用参数*
发送给接收方'Hello'
。事实上,如果你愿意,你可以像这样交替写它,这使事实更加明显:
5
'Hello'.*(5)
决定如何响应该消息。
而在
'Hello'
由5 * 'Hello'
来决定。
所以,答案的第一层是: OO中的消息发送本质上是非交换的,无论如何都没有对交换性的期望。
但是,现在问题变成了,为什么我们不设计一些交换性?例如,一种可能的方法是将二进制运算符解释为不将消息发送到其中一个操作数,而是将消息发送到某个第三个对象。例如,我们可以解释
5
as
5 * 'Hello'
和
*(5, 'Hello')
as
'Hello' * 5
即。消息发送到*('Hello', 5)
。现在,接收器在两种情况下是相同的,并且接收器可以安排自己以相同的方式处理这两种情况,从而使self
可交换。
另一种类似的可能性是使用某种共享上下文对象,例如,制作
*
相当于
5 * 'Hello'
事实上,在数学中,符号的含义通常取决于背景,例如:在ℤ中,Operators.*(5, 'Hello')
未定义,在ℚ中,它是2 / 3
,在IEEE754中,它与2/3
接近但不完全相同。或者,在ℤ中,0.333…
为2 * 3
,但在sub | 5 中,6
为2 * 3
。
所以,这样做肯定是有意义的。唉,它还没有完成。
另一种可能性是使两个操作数使用标准协议进行协作。实际上,对于1
上的算术运算,实际上是这样的协议!如果接收器不知道如何处理操作数,它可以向coerce
本身,接收器或两者请求操作数到接收器 知道如何处理的东西
基本上,协议是这样的:
Numeric
5 * 'Hello'
不知道如何处理5
,因此要求'Hello'
强制执行。 ... 'Hello'
来电5
'Hello'.coerce(5)
使用一对对象'Hello'
(作为[a, b]
)进行回复,以使Array
具有所需的结果a * b
来电5
一个常见的技巧是简单地实现a * b
来翻转操作数,这样当coerce
重试操作时,5
将成为接收者:
'Hello'
好吧,面向对象本质上是非交换性的,但我们可以通过合作使其成为可交换的,那么为什么不做呢?我必须承认,我对这个问题没有明确的答案,但我可以提供两个有根据的猜测:
class String
def coerce(other)
[self, other]
end
end
5 * 'Hello'
#=> 'HelloHelloHelloHelloHello'
专门用于算术运算中的数值强制。 (注意,协议在Numeric
中定义。)字符串不是数字,字符串连接也不是算术运算。coerce
与*
和Integer
等完全不同的类型进行交换。当然,为了好玩,我们实际上可以观察到 String
和Integer
之间存在某种对称性。实际上,您可以为String
和Integer#*
参数实现String
的通用版本,您将看到唯一的区别在于我们选择的&#34;零& #34;元素:
Integer
当然,原因是具有连接操作的字符串集和作为标识元素的空字符串形成 monoid ,就像具有串联的数组和空数组一样就像添加和零的整数一样。由于这三个都是幺半群,而且我们的乘法是重复加法&#34;只需要monoid操作和法则,它将适用于所有幺半群。
注意:Python对这种双重调度思想有一个有趣的转折。就像在Ruby中一样,如果你写的话
class Integer
def *(other)
zero = case other
when Integer then 0
when String then ''
when Array then []
end
times.inject(zero) {|acc, _| acc + other }
end
end
5 * 6
#=> 30
5 * 'six'
#=> 'sixsixsixsixsix'
5 * [:six]
#=> [:six, :six, :six, :six, :six, :six]
Python会将其重新写入发送消息:
a * b
但是,如果a.__mul__(b)
无法处理操作,而不是与a
合作,则会通过返回b
与Python合作。现在,Python将尝试NotImplemented
,但稍微扭曲:它将调用
b
这允许b.__rmul__(a)
知道它位于运营商的右侧。它对乘法来说并不重要(因为乘法是(通常但并不总是,参见例如矩阵乘法)可交换),但请记住运算符符号与它们的操作不同秒。因此,相同的运算符符号可用于可交换的操作和非交换的操作。示例:b
在Ruby中用于添加(+
),也用于连接(2 + 3 == 3 + 2
)。因此,对象确实知道它是右操作数还是左操作数实际上是有利的。
答案 2 :(得分:0)
好吧,因为Muntasir Alam已经告诉Fixnum
没有一个名为*
的方法,它以string
为参数。因此,5*"Hello"
会产生错误。但是,为了获得乐趣,我们可以通过向5*"Hello"
类添加缺少的方法来实现Fixnum
。
class Fixnum # open the class
def * str # Override the *() method
if str.is_a? String # If argument is String
temp = ""
self.times do
temp << str
end
temp
else # If the argument is not String
mul = 0
self.times do
mul += str
end
mul
end
end
end
现在
puts 5*"Hello"
#=&gt; HelloHelloHelloHelloHello
puts 4*5
#=&gt; 20
puts 5*10.4
#=&gt; 52.0
嗯,这只是为了表明相反的情况也是可能的。但这会带来很多开销。我认为我们应该不惜一切代价避免这种情况。