在Ruby中,coerce()实际上是如何工作的?
据说,当我们有一个类的Point
并知道如何执行point * 3
如下所示:
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
)
所以我们需要进一步定义一个实例方法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
上调用。
现在,由于这种方法*
不知道如何乘以一个点,所以
point.coerce(3)
将被调用,并返回一个数组:
[point, 3]
然后*
再一次应用于它,这是真的吗?
现在,这已经被理解了,我们现在有一个新的Point
对象,由Point
类的实例方法*
执行。
问题是:
-
谁调用
point.coerce(3)
? 它是自动的Ruby,还是它的一些代码*
Fixnum
的方法捕捉exception? 还是通过case
说明,当它不知道一个已知的types,然后调用coerce
? -
coerce
总是需要返回2个元素的数组? 它可以是没有数组? 或者它可以是3个元素的数组? -
而原则运算符(或方法)
*
然后将在元素0上调用元素1的参数? (元素0和元素1是coerce
返回的数组中的两个元素。)谁做的? 是由Ruby完成,还是由Fixnum
的代码完成? 如果是通过Fixnum
的代码完成的,那么这是一个人们在强迫时所遵循的“约定”吗?那么
Fixnum
*
代码可以这样做: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
真的很难? 它已经有了很多的代码,我们不能只是添加几行来增强它(但我们会想吗?) -
Point
类中的coerce
是非常通用的,它可以和*
或+
一起工作,因为它们是可传递的。 如果它不是传递的,例如如果我们将Point减去Fixnum定义为:point = Point.new(100,100) point - 20 #=> (80,80) 20 - point #=> (-80,-80)
简短的回答:看看Matrix
是如何做的 。
这个想法是coerce
返回[equivalent_something, equivalent_self]
,其中equivalent_something
是一个对象,基本上等同于something
但是知道如何在您的Point
类上执行操作。 在Matrix
,我们从任何一个Numeric
对象构造一个Matrix::Scalar
,并且该类知道如何在Matrix
和Vector
上执行操作。
为了解决您的观点:
-
是的,它直接是Ruby(检查源代码中的
rb_num_coerce_bin
调用),但是如果您希望您的代码可以被其他人扩展,那么您自己的types也应该这样做。 例如,如果你的Point#*
被传递了一个它不能识别的参数,你可以通过调用arg.coerce(self)
来请求这个参数coerce
它自己。 -
是的,它必须是2个元素的数组,这样
b_equiv, a_equiv = a.coerce(b)
-
是。 Ruby为内buildtypes做了它,如果你想要可扩展的话,你也应该在自己的自定义types上:
def *(arg) if (arg is not recognized) self_equiv, arg_equiv = arg.coerce(self) self_equiv * arg_equiv end end
-
这个想法是,你不应该修改
Fixnum#*
。 如果它不知道该怎么做,例如因为参数是一个Point
,那么它会通过调用Point#coerce
来问你。 -
传递性(或实际交换性)不是必需的,因为操作符总是以正确的顺序调用。 这只是
coerce
暂时恢复收到的和论点。 没有内build的机制确保运算符像+
,==
等的交换性。
如果有人能拿出简洁明了的描述来改善官方文档,请发表评论!
我发现自己经常在处理交换时,沿着这个模式编写代码:
class Foo def initiate(some_state) #... end def /(n) # code that handles Foo/n end def *(n) # code that handles Foo * n end def coerce(n) [ReverseFoo.new(some_state),n] end end class ReverseFoo < Foo def /(n) # code that handles n/Foo end # * commutes, and can be inherited from Foo end