在Ruby中,coerce()实际上是如何工作的?

时间:2010-05-09 23:26:43

标签: ruby coercion coerce type-coercion

据说当我们有一个班级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方法中的一些代码?或者是case声明,当它不知道其中一种已知类型时,请拨打coerce

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

  3. 规则是,然后在元素0上调用原始运算符(或方法)*,并使用元素1的参数? (元素0和元素1是coerce返回的数组中的两个元素。)是谁做的?是由Ruby完成还是由Fixnum中的代码完成?如果它是由Fixnum中的代码完成的,那么这是一个每个人在做强制时都遵循的“惯例”吗?

    *的{​​{1}}中的代码可能是这样的:

    Fixnum
  4. 因此,向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添加内容真的很难吗?它已经有很多代码,我们不能只添加几行来增强它(但我们想要吗?)

  5. coerce类中的coerce非常通用,可与Point*配合使用,因为它们具有传递性。如果它不是传递的,例如我们将Point减去Fixnum定义为:

    +

2 个答案:

答案 0 :(得分:42)

简短回答:查看how Matrix is doing it

我的想法是coerce返回[equivalent_something, equivalent_self],其中equivalent_something是一个基本等同于something的对象,但知道如何对Point进行操作类。在Matrix lib中,我们从任何Numeric对象构造Matrix::Scalar,该类知道如何对MatrixVector执行操作。

解决您的观点:

  1. 是的,它直接是Ruby(检查对rb_num_coerce_bin in the source的调用),但是如果您希望代码可以被其他人扩展,那么您自己的类型也应该这样做。例如,如果您的Point#*传递了一个无法识别的参数,您可以通过调用coercePoint本身询问该参数arg.coerce(self)

  2. 是的,它必须是包含2个元素的数组,例如b_equiv, a_equiv = a.coerce(b)

  3. 是。 Ruby为内置类型做了它,如果你想要可扩展,你也应该使用自己的自定义类型:

    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的调用,它暂时恢复收到的和参数。没有内置机制可以确保+==等运营商的交换性......

  6. 如果有人能够提出简洁,准确和清晰的描述来改进官方文档,请发表评论!

答案 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