クラス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
オブジェクトができました。
質問は:
誰がpoint.coerce(3)
を呼び出しますか?それは自動的にRuby)ですか、それとも例外をキャッチすることによってFixnum
の_*
_メソッド内のコードですか?それともcase
ステートメントによるものですか?既知のタイプの1つがわからない場合は、coerce
を呼び出しますか?
coerce
は常に2つの要素の配列を返す必要がありますか?配列ではありませんか?または、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
_
したがって、Fixnum
のインスタンスメソッドcoerce
に何かを追加するのは本当に難しいですか?すでに多くのコードが含まれているため、数行追加して拡張することはできません(ただし、必要になることはありますか?)
coerce
クラスのPoint
は非常に一般的であり、推移的であるため、_*
_または_+
_で機能します。 PointからFixnumを引いたものを次のように定義する場合など、推移的でない場合はどうなりますか。
_point = Point.new(100,100)
point - 20 #=> (80,80)
20 - point #=> (-80,-80)
_
簡単な答え:チェックアウト Matrix
がどのようにそれを行っているか 。
coerce
は_[equivalent_something, equivalent_self]
_を返すという考え方です。ここで、_equivalent_something
_は基本的にsomething
と同等のオブジェクトですが、Point
クラスで操作を行う方法を知っています。 Matrix
libでは、任意のNumeric
オブジェクトから _Matrix::Scalar
_ を構築し、そのクラスはMatrix
およびVector
で操作を実行する方法を知っています。
あなたのポイントに対処するには:
はい、それはRuby直接(ソースの _rb_num_coerce_bin
_への呼び出しをチェック )ですが、コードを他の人が拡張可能。たとえば、_Point#*
_に認識できない引数が渡された場合、arg.coerce(self)
を呼び出して、coerce
自体への引数をPoint
に要求します。
はい、b_equiv, a_equiv = a.coerce(b)
のように、2つの要素の配列である必要があります
はい。 Rubyは組み込み型に対して実行します。拡張可能にしたい場合は、独自のカスタム型も使用する必要があります。
_def *(arg)
if (arg is not recognized)
self_equiv, arg_equiv = arg.coerce(self)
self_equiv * arg_equiv
end
end
_
_Fixnum#*
_を変更しないでください。引数がPoint
であるなどの理由で何をすべきかわからない場合は、_Point#coerce
_を呼び出して尋ねます。
演算子は常に正しい順序で呼び出されるため、推移性(または実際には可換性)は必要ありません。受信した引数と引数を一時的に元に戻すのは、coerce
の呼び出しだけです。 _+
_、_==
_などの演算子の可換性を保証する組み込みのメカニズムはありません。
誰かが公式ドキュメントを改善するために簡潔で正確で明確な説明を思い付くことができるなら、コメントを残してください!
可換性を扱うとき、私はしばしばこのパターンに沿ってコードを書くことに気づきます。
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