In Ruby, how does coerce() actually work?
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:
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 yourPoint#*
is passed an argument it doesn't recognize, you would ask that argument tocoerce
itself to aPoint
by callingarg.coerce(self)
.Yes, it has to be an Array of 2 elements, such that
b_equiv, a_equiv = a.coerce(b)
-
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
The idea is that you shouldn't modify
Fixnum#*
. If it doesn't know what to do, for example because the argument is aPoint
, then it will ask you by callingPoint#coerce
.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!
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