在Ruby中,coerce()实际如何工作? [英] In Ruby, how does coerce() actually work?
问题描述
据说当我们有一个类Point
并知道如何执行point * 3
时,如下所示:
It is said that when we have a class Point
and knows how to perform point * 3
like the following:
class Point
def initialize(x,y)
@x, @y = x, y
end
def *(c)
Point.new(@x * c, @y * c)
end
end
point = Point.new(1,2)
p point
p point * 3
输出:
#<Point:0x336094 @x=1, @y=2>
#<Point:0x335fa4 @x=3, @y=6>
但是,
3 * point
无法理解:
Point
不能强制进入Fixnum
(TypeError
)
Point
can't be coerced intoFixnum
(TypeError
)
因此,我们需要进一步定义实例方法coerce
:
So we need to further define an instance method coerce
:
class Point
def coerce(something)
[self, something]
end
end
p 3 * point
输出:
#<Point:0x3c45a88 @x=3, @y=6>
因此,可以说3 * point
与3.*(point)
相同.也就是说,实例方法*
接受参数point
并在对象3
上调用.
So it is said that 3 * point
is the same as 3.*(point)
. That is, the instance method *
takes an argument point
and invoke on the object 3
.
现在,由于此方法*
不知道如何相乘一个点,所以
Now, since this method *
doesn't know how to multiply a point, so
point.coerce(3)
将被调用,并返回一个数组:
will be called, and get back an array:
[point, 3]
然后再次将*
应用于它,对吗?
and then *
is once again applied to it, is that true?
现在,这已经可以理解了,我们现在有了一个新的Point
对象,该对象由Point
类的实例方法*
执行.
Now, this is understood and we now have a new Point
object, as performed by the instance method *
of the Point
class.
问题是:
-
谁调用
point.coerce(3)
?它是Ruby自动生成,还是通过捕获异常在Fixnum
的*
方法内部的某些代码?还是通过case
语句在不知道已知类型之一时调用coerce
?
Who invokes
point.coerce(3)
? Is it Ruby automatically, or is it some code inside of*
method ofFixnum
by catching an exception? Or is it bycase
statement that when it doesn't know one of the known types, then callcoerce
?
coerce
是否总是需要返回2个元素的数组?可以没有数组吗?还是可以是3个元素组成的数组?
Does coerce
always need to return an array of 2 elements? Can it be no array? Or can it be an array of 3 elements?
这是规则,即原始运算符(或方法)*
然后将使用元素1的参数在元素0上被调用吗? (元素0和元素1是coerce
返回的该数组中的两个元素.)是谁?是由Ruby完成还是由Fixnum
中的代码完成?如果是通过Fixnum
中的代码完成的,那么这是每个人在强制执行时都会遵循的惯例"吗?
And is the rule that, the original operator (or method) *
will then be invoked on element 0, with the argument of element 1? (Element 0 and element 1 are the two elements in that array returned by coerce
.) Who does it? Is it done by Ruby or is it done by code in Fixnum
? If it is done by code in Fixnum
, then it is a "convention" that everybody follows when doing a coercion?
那么Fixnum
的*
中的代码是否可以执行以下操作:
So could it be the code in *
of Fixnum
doing something like this:
class Fixnum
def *(something)
if (something.is_a? ...)
else if ... # other type / class
else if ... # other type / class
else
# it is not a type / class I know
array = something.coerce(self)
return array[0].*(array[1]) # or just return array[0] * array[1]
end
end
end
因此很难在Fixnum
的实例方法coerce
中添加一些内容?它已经有很多代码,我们不能仅仅添加几行代码来增强它(但是我们会想要吗?)
So it is really hard to add something to Fixnum
's instance method coerce
? It already has a lot of code in it and we can't just add a few lines to enhance it (but will we ever want to?)
Point
类中的coerce
非常通用,并且可与*
或+
一起使用,因为它们是可传递的.如果它不是可传递的,例如将Point减去Fixnum定义为:
The coerce
in the Point
class is quite generic and it works with *
or +
because they are transitive. What if it is not transitive, such as if we define Point minus Fixnum to be:
point = Point.new(100,100)
point - 20 #=> (80,80)
20 - point #=> (-80,-80)
推荐答案
Short answer: check out how Matrix
is doing it.
这个想法是coerce
返回[equivalent_something, equivalent_self]
,其中equivalent_something
是基本上等同于something
的对象,但是知道如何对Point
类进行操作.在Matrix
库中,我们构造了 Matrix::Scalar
来自任何Numeric
对象,并且该类知道如何对Matrix
和Vector
执行操作.
The idea is that coerce
returns [equivalent_something, equivalent_self]
, where equivalent_something
is an object basically equivalent to something
but that knows how to do operations on your Point
class. In the Matrix
lib, we construct a Matrix::Scalar
from any Numeric
object, and that class knows how to perform operations on Matrix
and Vector
.
要解决您的问题,
-
是的,直接是Ruby(检查对
rb_num_coerce_bin
(在源代码中)),但是如果您希望代码可以被其他人扩展,那么您自己的类型也应该这样做.例如,如果您的Point#*
传递了一个无法识别的参数,则您可以通过调用arg.coerce(self)
向Point
询问coerce
本身的参数.
Yes, it is Ruby directly (check calls to
rb_num_coerce_bin
in the source), although your own types should do too if you want your code to be extensible by others. For example if yourPoint#*
is passed an argument it doesn't recognize, you would ask that argument tocoerce
itself to aPoint
by callingarg.coerce(self)
.
是的,它必须是2个元素组成的数组,这样b_equiv, a_equiv = a.coerce(b)
Yes, it has to be an Array of 2 elements, such that b_equiv, a_equiv = a.coerce(b)
是的. Ruby针对内置类型执行此操作,如果您希望扩展性,也应该使用自己的自定义类型:
Yes. Ruby does it for builtin types, and you should too on your own custom types if you want to be extensible:
def *(arg)
if (arg is not recognized)
self_equiv, arg_equiv = arg.coerce(self)
self_equiv * arg_equiv
end
end
这个想法是,您不应该修改Fixnum#*
.如果它不知道要做什么,例如因为参数为Point
,那么它将通过调用Point#coerce
来询问您.
The idea is that you shouldn't modify Fixnum#*
. If it doesn't know what to do, for example because the argument is a Point
, then it will ask you by calling Point#coerce
.
不需要传递性(或实际上是可交换性),因为始终以正确的顺序调用运算符.只是对coerce
的调用会临时还原接收到的参数.没有内置的机制可以确保+
,==
等运算符的可交换性.
Transitivity (or actually commutativity) is not necessary, because the operator is always called in the right order. It's only the call to coerce
which temporarily reverts the received and the argument. There is no builtin mechanism that insures commutativity of operators like +
, ==
, etc...
如果有人可以提出简洁,准确和清晰的描述来改进官方文档,请发表评论!
If someone can come up with a terse, precise and clear description to improve the official documentation, leave a comment!
这篇关于在Ruby中,coerce()实际如何工作?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!