In Ruby, how does coerce() actually work?

后端 未结 2 1963
栀梦
栀梦 2020-11-30 20:12

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

class Point
  def initialize(x,y)         


        
相关标签:
2条回答
  • 2020-11-30 20:48

    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.

    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).

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

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

      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.

    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!

    0 讨论(0)
  • 2020-11-30 20:52

    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
    
    0 讨论(0)
提交回复
热议问题