在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不能被强制转换成FixnumTypeError

所以我们需要进一步定义一个实例方法coerce

 class Point def coerce(something) [self, something] end end p 3 * point 

输出:

 #<Point:0x3c45a88 @x=3, @y=6> 

所以说3 * point3.*(point) 。 也就是说,实例方法*接受一个参数point并在对象3上调用。

现在,由于这种方法*不知道如何乘以一个点,所以

 point.coerce(3) 

将被调用,并返回一个数组:

 [point, 3] 

然后*再一次应用于它,这是真的吗?

现在,这已经被理解了,我们现在有一个新的Point对象,由Point类的实例方法*执行。

问题是:

  1. 谁调用point.coerce(3) ? 它是自动的Ruby,还是它的一些代码* Fixnum的方法捕捉exception? 还是通过case说明,当它不知道一个已知的types,然后调用coerce

  2. coerce总是需要返回2个元素的数组? 它可以是没有数组? 或者它可以是3个元素的数组?

  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 
  4. 所以要增加一些Fixnum的实例方法coerce真的很难? 它已经有了很多的代码,我们不能只是添加几行来增强它(但我们会想吗?)

  5. 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 ,并且该类知道如何在MatrixVector上执行操作。

为了解决您的观点:

  1. 是的,它直接是Ruby(检查源代码中的rb_num_coerce_bin调用),但是如果您希望您的代码可以被其他人扩展,那么您自己的types也应该这样做。 例如,如果你的Point#*被传递了一个它不能识别的参数,你可以通过调用arg.coerce(self)来请求这个参数coerce它自己。

  2. 是的,它必须是2个元素的数组,这样b_equiv, a_equiv = a.coerce(b)

  3. 是。 Ruby为内buildtypes做了它,如果你想要可扩展的话,你也应该在自己的自定义types上:

     def *(arg) if (arg is not recognized) self_equiv, arg_equiv = arg.coerce(self) self_equiv * arg_equiv end end 
  4. 这个想法是,你不应该修改Fixnum#* 。 如果它不知道该怎么做,例如因为参数是一个Point ,那么它会通过调用Point#coerce来问你。

  5. 传递性(或实际交换性)不是必需的,因为操作符总是以正确的顺序调用。 这只是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 
Interesting Posts