在 Ruby 中, coerce() 是如何工作的?
Posted
技术标签:
【中文标题】在 Ruby 中, coerce() 是如何工作的?【英文标题】:In Ruby, how does coerce() actually work? 【发布时间】: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)
【问题讨论】:
这是一个很好的问题!我很高兴我找到了它,因为这一直困扰着我,直到现在我才认为它是可以解决的! 一个很好的问题。谢谢你把它。我敢肯定,这将节省许多工程师的困惑时间。 【参考方案1】:我发现自己在处理可交换性时经常按照这种模式编写代码:
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
【讨论】:
对于任何将来阅读本文的人:如果可以轻松地将数字直接转换为您的格式,我认为更建议使用[Foo.instantiate(n), self]
。【参考方案2】:
简答:查看how Matrix
is doing it。
这个想法是coerce
返回[equivalent_something, equivalent_self]
,其中equivalent_something
是一个基本上等同于something
的对象,但它知道如何对Point
类进行操作。在Matrix
库中,我们从任何Numeric
对象构造一个Matrix::Scalar
,并且该类知道如何对Matrix
和Vector
执行操作。
解决您的问题:
是的,它直接是 Ruby(检查对 rb_num_coerce_bin
in the source 的调用),尽管如果您希望您的代码可以被其他人扩展,您自己的类型也应该这样做。例如,如果您的Point#*
被传递了一个它无法识别的参数,您可以通过调用arg.coerce(self)
来将该参数传递给coerce
本身到Point
。
是的,它必须是一个包含 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
的调用会暂时恢复接收到的参数。没有内置机制可以确保 +
、==
等运算符的交换性......
如果有人能提出简洁、准确和清晰的描述来改进官方文档,请发表评论!
【讨论】:
嗯,传递性实际上没有影响吗?例如,请参阅***.com/questions/2801241/… 不,传递性没有任何作用,Ruby 不认为a - b
与-(b - a)
或类似的东西相同,甚至a + b == b + a
也没有。是什么让你相信我错了?你检查过核磁共振的来源吗?为什么不尝试按照我指示的方向?
我认为 OP 的意思是“对称”而不是“传递”。在任何情况下,我都想知道您如何编写coerce
,这样非对称运算符(如-
)只能在一个方向上实现,同时保持对称运算符双向工作。换句话说,a + 3 == 3 + a
和 3 + a - 3 == a
但3 - a
会引发错误。
@OldPro 通过“对称”我假设您的意思是“交换”。但我同意,OP 可能意味着可交换,而不是传递。传递性与关系(例如相等)有关,而不是加法/减法之类的操作。
没有什么可以保证交换性。即使不涉及强制。参见例如blog.marc-andre.ca/2009/05/02/schizo-ruby-puzzle以上是关于在 Ruby 中, coerce() 是如何工作的?的主要内容,如果未能解决你的问题,请参考以下文章
在 Ruby 中,“=>”是啥意思,它是如何工作的? [复制]
Pandas:errors=‘coerce‘ & infer_datetime_format=True