web-dev-qa-db-ja.com

Rubyでは、coerce()は実際にどのように機能しますか?

クラス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
_

理解されていない:

PointFixnumに強制変換することはできません(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ステートメントによるものですか?既知のタイプの1つがわからない場合は、coerceを呼び出しますか?

  2. coerceは常に2つの要素の配列を返す必要がありますか?配列ではありませんか?または、3つの要素の配列にすることはできますか?

  3. そして、元の演算子(またはメソッド)_*_が要素1の引数を使用して、要素0で呼び出されるという規則はありますか? (要素0と要素1は、coerceによって返されるその配列内の2つの要素です。)誰がそれを行いますか? 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は非常に一般的であり、推移的であるため、_*_または_+_で機能します。 PointからFixnumを引いたものを次のように定義する場合など、推移的でない場合はどうなりますか。

    _point = Point.new(100,100)
    point - 20  #=> (80,80)
    20 - point  #=> (-80,-80)
    _
61
nonopolarity

簡単な答え:チェックアウト Matrixがどのようにそれを行っているか

coerceは_[equivalent_something, equivalent_self]_を返すという考え方です。ここで、_equivalent_something_は基本的にsomethingと同等のオブジェクトですが、Pointクラスで操作を行う方法を知っています。 Matrix libでは、任意のNumericオブジェクトから _Matrix::Scalar_ を構築し、そのクラスはMatrixおよびVectorで操作を実行する方法を知っています。

あなたのポイントに対処するには:

  1. はい、それはRuby直接(ソースの _rb_num_coerce_bin_への呼び出しをチェック )ですが、コードを他の人が拡張可能。たとえば、_Point#*_に認識できない引数が渡された場合、arg.coerce(self)を呼び出して、coerce自体への引数をPointに要求します。

  2. はい、b_equiv, a_equiv = a.coerce(b)のように、2つの要素の配列である必要があります

  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の呼び出しだけです。 _+_、_==_などの演算子の可換性を保証する組み込みのメカニズムはありません。

誰かが公式ドキュメントを改善するために簡潔で正確で明確な説明を思い付くことができるなら、コメントを残してください!

42

可換性を扱うとき、私はしばしばこのパターンに沿ってコードを書くことに気づきます。

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
2
stobix