【发布时间】:2011-02-17 11:30:20
【问题描述】:
据说当我们有一个类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中的代码来做的话,那么就是大家在做强制时都遵循的“约定”?那么可能是
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)
【问题讨论】:
-
这是一个很好的问题!我很高兴我找到了它,因为这一直困扰着我,直到现在我才认为它是可以解决的!
-
一个很好的问题。谢谢你把它。我敢肯定,这将节省许多工程师的困惑时间。
标签: ruby coercion coerce type-coercion