据说当我们有一个班级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
方法中的一些代码?或者是case
声明,当它不知道其中一种已知类型时,请拨打coerce
?
coerce
总是需要返回2个元素的数组吗?可以没有阵列吗?或者它可以是3个元素的数组?
规则是,然后在元素0上调用原始运算符(或方法)*
,并使用元素1的参数? (元素0和元素1是coerce
返回的数组中的两个元素。)是谁做的?是由Ruby完成还是由Fixnum
中的代码完成?如果它是由Fixnum
中的代码完成的,那么这是一个每个人在做强制时都遵循的“惯例”吗?
*
的{{1}}中的代码可能是这样的:
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
类中的coerce
非常通用,可与Point
或*
配合使用,因为它们具有传递性。如果它不是传递的,例如我们将Point减去Fixnum定义为:
+
答案 0 :(得分:42)
简短回答:查看how Matrix
is doing it。
我的想法是coerce
返回[equivalent_something, equivalent_self]
,其中equivalent_something
是一个基本等同于something
的对象,但知道如何对Point
进行操作类。在Matrix
lib中,我们从任何Numeric
对象构造Matrix::Scalar
,该类知道如何对Matrix
和Vector
执行操作。
解决您的观点:
是的,它直接是Ruby(检查对rb_num_coerce_bin
in the source的调用),但是如果您希望代码可以被其他人扩展,那么您自己的类型也应该这样做。例如,如果您的Point#*
传递了一个无法识别的参数,您可以通过调用coerce
向Point
本身询问该参数arg.coerce(self)
。
是的,它必须是包含2个元素的数组,例如b_equiv, a_equiv = a.coerce(b)
是。 Ruby为内置类型做了它,如果你想要可扩展,你也应该使用自己的自定义类型:
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
的调用,它暂时恢复收到的和参数。没有内置机制可以确保+
,==
等运营商的交换性......
如果有人能够提出简洁,准确和清晰的描述来改进官方文档,请发表评论!
答案 1 :(得分:2)
在处理交换时,我发现自己经常在这种模式下编写代码:
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