据说,当我们有一个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 * 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中的代码完成的,那么它是每个人在进行胁迫时都遵循的"约定"?

    那么可能是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. Point类中的coerce是非常通用的,它与*+一起使用,因为它们是可传递的.如果它不是可传递的,比如我们定义Point减go Fixnum为:

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

推荐答案

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

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

为了说明你的观点:

  1. 是的,它是Ruby Direct(判断对rb_num_coerce_bin in the source的调用),不过如果您希望自己的代码能够被其他人扩展,您自己的类型也应该这样做.例如,如果您的Point#*被传递了一个它无法识别的参数,您可以通过调用arg.coerce(self),将该参数本身转换为coerce,再转换为Point.

  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. 这个 idea 是你不应该修改Fixnum#*.如果它不知道该做什么,例如因为参数是Point,那么它会通过拨打Point#coerce来询问您.

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

如果有人能提出一个简洁、准确、清晰的描述来改进官方文件,请留言!

Ruby相关问答推荐

如何在将拆分元素保留为空字符串的同时拆分字符串?

重写 Enum#inject 以在 Ruby 中将符号作为参数

整数除以负数

RSpec 模拟对象示例

get.chomp() 与 STDIN.gets.chomp() 有什么区别?

如何记录在 Ruby 程序中调用的每个方法?

这些 Ruby 命名空间约定之间有什么区别?

在 Ruby 早期转义 .each { } 迭代

如何判断 Capistrano 中是否存在文件(在远程服务器上)?

Ruby 中的 Object 和 BasicObject 有什么区别?

用反斜杠单引号替换单引号

卸载所有 gem Ruby 2.0.0

Broken pipe (Errno::EPIPE)

如何仅从 Gemfile 中查看依赖关系树?

如何在 ruby​​ 中进行命名捕获

如何通过反射获得活动记录关联

如何在课堂上使用 Enumerable mixin?

是否有更简单的(单行)语法来别名一个类方法?

是否可以使用 Ruby 读取文件的修改日期?

如何在 jekyll markdown 博客中包含视频