61

据说当我们有课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

问题是:

  1. 谁调用point.coerce(3)?是自动使用Ruby,还是通过捕获异常在*方法内部的一些代码?Fixnum或者是通过case声明当它不知道其中一种已知类型时,然后调用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. coerce类中的Point非常通用,它可以与*or一起使用,+因为它们是可传递的。如果它不具有传递性,例如我们将 Point 减去 Fixnum 定义为:

    point = Point.new(100,100)
    point - 20  #=> (80,80)
    20 - point  #=> (-80,-80)
    
4

2 回答 2

42

简短的回答:看看是怎么Matrix做的

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

为了解决您的观点:

  1. rb_num_coerce_bin是的,它直接是 Ruby(检查源代码中的调用),尽管如果您希望您的代码可以被其他人扩展,您自己的类型也应该这样做。例如,如果您Point#*传递了一个它无法识别的参数,您可以通过调用coerce来向 a询问该参数。Pointarg.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#*. 如果它不知道该怎么做,例如因为参数是 a Point,那么它会通过调用 来询问你Point#coerce

  5. 传递性(或实际上的交换性)不是必需的,因为操作符总是以正确的顺序被调用。只有coerce临时恢复接收到的参数的调用。没有内置机制可以确保 , 等运算符的交换+==......

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

于 2010-05-10T01:38:31.420 回答
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
于 2013-04-23T10:18:25.813 回答