在Ruby中,矫顽磁力()是如何工作的?

时间:2022-08-25 17:26:32

It is said that when we have a class Point and knows how to perform point * 3 like the following:

据说,当我们有一个类点并且知道如何执行点* 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

Output:

输出:

#<Point:0x336094 @x=1, @y=2>
#<Point:0x335fa4 @x=3, @y=6>

but then,

但是,

3 * point

is not understood:

不理解:

Point can't be coerced into Fixnum (TypeError)

不能将点强制为Fixnum (TypeError)

So we need to further define an instance method coerce:

因此我们需要进一步定义一个实例方法ce:

class Point
  def coerce(something)
    [self, something]
  end
end

p 3 * point

Output:

输出:

#<Point:0x3c45a88 @x=3, @y=6>

So it is said that 3 * point is the same as 3.*(point). That is, the instance method * takes an argument point and invoke on the object 3.

所以说3 *点等于3。*(点)。也就是说,实例方法*获取一个参数点并在对象3上调用。

Now, since this method * doesn't know how to multiply a point, so

因为这个方法*不知道怎么乘一个点,所以

point.coerce(3)

will be called, and get back an array:

将被调用,并返回一个数组:

[point, 3]

and then * is once again applied to it, is that true?

然后*再一次应用到它,是真的吗?

Now, this is understood and we now have a new Point object, as performed by the instance method * of the Point class.

现在,我们理解了这一点,现在我们有了一个新的Point对象,由Point类的实例方法*执行。

The question is:

问题是:

  1. Who invokes point.coerce(3)? Is it Ruby automatically, or is it some code inside of * method of Fixnum by catching an exception? Or is it by case statement that when it doesn't know one of the known types, then call coerce?

    谁调用point.coerce(3)?它是自动的Ruby,还是在* Fixnum方法中捕获异常的代码?还是说,根据案例陈述,当它不知道已知类型之一时,就调用矫顽磁力?

  2. Does coerce always need to return an array of 2 elements? Can it be no array? Or can it be an array of 3 elements?

    强制是否总是需要返回一个包含两个元素的数组?它不是数组吗?或者它是由3个元素组成的数组?

  3. And is the rule that, the original operator (or method) * will then be invoked on element 0, with the argument of element 1? (Element 0 and element 1 are the two elements in that array returned by coerce.) Who does it? Is it done by Ruby or is it done by code in Fixnum? If it is done by code in Fixnum, then it is a "convention" that everybody follows when doing a coercion?

    那么,在元素0上使用元素1的参数调用原始运算符(或方法)*的规则是什么呢?(元素0和元素1是由强制返回的数组中的两个元素)。谁做吗?它是由Ruby完成的还是由Fixnum中的代码完成的?如果它是由Fixnum中的代码完成的,那么它是每个人在强制执行时都遵循的“约定”吗?

    So could it be the code in * of Fixnum doing something like this:

    那么可能是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. So it is really hard to add something to Fixnum's instance method coerce? It already has a lot of code in it and we can't just add a few lines to enhance it (but will we ever want to?)

    那么向Fixnum的实例方法强制添加一些东西真的很难吗?它已经有很多代码了,我们不能只添加几行来增强它(但是我们会想要吗?)

  5. The coerce in the Point class is quite generic and it works with * or + because they are transitive. What if it is not transitive, such as if we define Point minus Fixnum to be:

    Point类中的强制是非常通用的,它与*或+一起工作,因为它们是可传递的。如果它不是传递性的,比如我们定义点-固定数为:

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

2 个解决方案

#1


40  

Short answer: check out how Matrix is doing it.

简短的回答:看看黑客帝国是怎么做的。

The idea is that coerce returns [equivalent_something, equivalent_self], where equivalent_something is an object basically equivalent to something but that knows how to do operations on your Point class. In the Matrix lib, we construct a Matrix::Scalar from any Numeric object, and that class knows how to perform operations on Matrix and Vector.

这里的想法是,强制返回[equivalent_something, equivalent_self],其中equivalent_something基本上等同于某物,但它知道如何对点类执行操作。在矩阵库中,我们从任何数字对象构造一个矩阵:标量,这个类知道如何对矩阵和向量执行操作。

To address your points:

解决你的观点:

  1. Yes, it is Ruby directly (check calls to rb_num_coerce_bin in the source), although your own types should do too if you want your code to be extensible by others. For example if your Point#* is passed an argument it doesn't recognize, you would ask that argument to coerce itself to a Point by calling arg.coerce(self).

    是的,它是直接使用Ruby的(在源代码中检查对rb_num_矫顽器的调用),但是如果您希望您的代码可被其他人扩展,您自己的类型也应该这样做。例如,如果您的Point#*传递了一个它不认识的参数,您将要求该参数通过调用arg. mandatory (self)将自己强制到某个点。

  2. Yes, it has to be an Array of 2 elements, such that b_equiv, a_equiv = a.coerce(b)

    是的,它必须是一个由2个元素组成的数组,例如b_equiv, a_equiv = a.c ece (b)

  3. Yes. Ruby does it for builtin types, and you should too on your own custom types if you want to be extensible:

    是的。Ruby用于构建类型,如果您想要扩展的话,也应该使用自己的自定义类型:

    def *(arg)
      if (arg is not recognized)
        self_equiv, arg_equiv = arg.coerce(self)
        self_equiv * arg_equiv
      end
    end
    
  4. The idea is that you shouldn't modify Fixnum#*. If it doesn't know what to do, for example because the argument is a Point, then it will ask you by calling Point#coerce.

    其想法是,您不应该修改Fixnum#*。如果它不知道该怎么做,例如,因为参数是一个点,那么它会通过调用点# force来问你。

  5. Transitivity (or actually commutativity) is not necessary, because the operator is always called in the right order. It's only the call to coerce which temporarily reverts the received and the argument. There is no builtin mechanism that insures commutativity of operators like +, ==, etc...

    传递性(或者实际的可交换性)是不必要的,因为运算符总是以正确的顺序被调用。这只是对强制的召唤,它暂时逆转了被接受和争论。没有内置的机制来保证运算符的交换性,例如+、==等等……

If someone can come up with a terse, precise and clear description to improve the official documentation, leave a comment!

如果有人能提出一个简洁、精确和清晰的描述来改进官方文档,请留下评论!

#2


1  

I find myself often writing code along this pattern when dealing with commutativity:

在处理交换性时,我发现自己经常在这种模式下编写代码:

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

#1


40  

Short answer: check out how Matrix is doing it.

简短的回答:看看黑客帝国是怎么做的。

The idea is that coerce returns [equivalent_something, equivalent_self], where equivalent_something is an object basically equivalent to something but that knows how to do operations on your Point class. In the Matrix lib, we construct a Matrix::Scalar from any Numeric object, and that class knows how to perform operations on Matrix and Vector.

这里的想法是,强制返回[equivalent_something, equivalent_self],其中equivalent_something基本上等同于某物,但它知道如何对点类执行操作。在矩阵库中,我们从任何数字对象构造一个矩阵:标量,这个类知道如何对矩阵和向量执行操作。

To address your points:

解决你的观点:

  1. Yes, it is Ruby directly (check calls to rb_num_coerce_bin in the source), although your own types should do too if you want your code to be extensible by others. For example if your Point#* is passed an argument it doesn't recognize, you would ask that argument to coerce itself to a Point by calling arg.coerce(self).

    是的,它是直接使用Ruby的(在源代码中检查对rb_num_矫顽器的调用),但是如果您希望您的代码可被其他人扩展,您自己的类型也应该这样做。例如,如果您的Point#*传递了一个它不认识的参数,您将要求该参数通过调用arg. mandatory (self)将自己强制到某个点。

  2. Yes, it has to be an Array of 2 elements, such that b_equiv, a_equiv = a.coerce(b)

    是的,它必须是一个由2个元素组成的数组,例如b_equiv, a_equiv = a.c ece (b)

  3. Yes. Ruby does it for builtin types, and you should too on your own custom types if you want to be extensible:

    是的。Ruby用于构建类型,如果您想要扩展的话,也应该使用自己的自定义类型:

    def *(arg)
      if (arg is not recognized)
        self_equiv, arg_equiv = arg.coerce(self)
        self_equiv * arg_equiv
      end
    end
    
  4. The idea is that you shouldn't modify Fixnum#*. If it doesn't know what to do, for example because the argument is a Point, then it will ask you by calling Point#coerce.

    其想法是,您不应该修改Fixnum#*。如果它不知道该怎么做,例如,因为参数是一个点,那么它会通过调用点# force来问你。

  5. Transitivity (or actually commutativity) is not necessary, because the operator is always called in the right order. It's only the call to coerce which temporarily reverts the received and the argument. There is no builtin mechanism that insures commutativity of operators like +, ==, etc...

    传递性(或者实际的可交换性)是不必要的,因为运算符总是以正确的顺序被调用。这只是对强制的召唤,它暂时逆转了被接受和争论。没有内置的机制来保证运算符的交换性,例如+、==等等……

If someone can come up with a terse, precise and clear description to improve the official documentation, leave a comment!

如果有人能提出一个简洁、精确和清晰的描述来改进官方文档,请留下评论!

#2


1  

I find myself often writing code along this pattern when dealing with commutativity:

在处理交换性时,我发现自己经常在这种模式下编写代码:

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