En Ruby, ¿cómo puede coaccionar () en realidad el trabajo?
-
04-10-2019 - |
Pregunta
Se dice que cuando tenemos una Point
clase y sabe cómo realizar point * 3
como la siguiente:
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
Salida:
#<Point:0x336094 @x=1, @y=2>
#<Point:0x335fa4 @x=3, @y=6>
Pero entonces,
3 * point
no se entiende:
Point
no puede ser obligado aFixnum
(TypeError
)
Por lo tanto, tenemos que definir, además, un método de instancia coerce
:
class Point
def coerce(something)
[self, something]
end
end
p 3 * point
Salida:
#<Point:0x3c45a88 @x=3, @y=6>
Así se dice que 3 * point
es la misma que 3.*(point)
. Es decir, el método de instancia *
toma un argumento point
e invocar en el 3
objeto.
Ahora, ya que este método *
no sabe cómo multiplicar un punto, por lo
point.coerce(3)
Se llamará, y volver una matriz:
[point, 3]
y luego *
se aplica una vez más a él, es eso cierto?
Ahora, esto se entiende y ahora tenemos un nuevo objeto Point
, interpretada por la *
método de instancia de la clase Point
.
La pregunta es:
-
Quien invoca
point.coerce(3)
? Es Rubí automáticamente, o se trata de algo de código dentro del método de*
Fixnum
por la captura de una excepción? O es por la declaracióncase
que cuando no sabe uno de los tipos conocidos, a continuación, llamarcoerce
? -
¿El
coerce
siempre hay que devolver una matriz de 2 elementos? ¿Puede ser ninguna matriz? O puede ser una matriz de 3 elementos? -
Y es la regla de que, el operador original (o método)
*
a continuación, se invoca en el elemento 0, con el argumento del elemento 1? (Elemento 0 y el elemento 1 son los dos elementos en que matriz devuelta porcoerce
.) ¿Quién lo hace? ¿Se hace por Rubí o está hecho por código enFixnum
? Si se hace por código enFixnum
, entonces es una "convención" que todo el mundo sigue cuando se hace una coacción?Así podría ser el código en
*
deFixnum
hacer algo como esto: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
-
Por lo tanto, es muy difícil añadir algo al método de instancia
Fixnum
decoerce
? Ya cuenta con una gran cantidad de código en él y no podemos simplemente añada unas cuantas líneas para mejorar (pero nunca vamos a querer?) -
El
coerce
en la clasePoint
es bastante genérico y funciona con*
o+
porque son transitivos. ¿Y si no es transitivo, como si definimos punto negativo Fixnum a ser:point = Point.new(100,100) point - 20 #=> (80,80) 20 - point #=> (-80,-80)
Solución
Respuesta corta: consulta cómo lo está haciendo Matrix
.
La idea es que los retornos coerce
[equivalent_something, equivalent_self]
, donde equivalent_something
es un objeto básicamente equivalente a something
pero que sabe hacer operaciones en su clase Point
. En el lib Matrix
, construimos un Matrix::Scalar
de cualquier Numeric
objeto, y que la clase sabe cómo realizar operaciones en Matrix
y Vector
.
Para hacer frente a sus puntos:
-
Sí, es Rubí directamente (VER llamadas a
rb_num_coerce_bin
en la fuente ), a pesar de sus propios tipos deben hacer también si usted quiere que su código sea extensible por otros. Por ejemplo si suPoint#*
se pasa un argumento que no reconoce, podría pedir que argumento paracoerce
sí a unPoint
llamandoarg.coerce(self)
. -
Sí, tiene que ser una matriz de 2 elementos, de manera que
b_equiv, a_equiv = a.coerce(b)
-
Sí. Rubí lo hace por tipos internos, y se debe también en sus propios tipos personalizados si quieren ser extensible:
def *(arg) if (arg is not recognized) self_equiv, arg_equiv = arg.coerce(self) self_equiv * arg_equiv end end
-
La idea es que no se debe modificar
Fixnum#*
. Si no sabe qué hacer, por ejemplo, porque el argumento es unaPoint
, a continuación, se le pedirá que llamandoPoint#coerce
. -
La transitividad (o en realidad conmutatividad) no es necesario, ya que el operador siempre se llama en el orden correcto. Es sólo la llamada a
coerce
que revierte temporalmente la acogida y el argumento. No existe un mecanismo incorporado que asegura conmutatividad de operadores como+
,==
, etc ...
Si alguien puede subir con un escueto, preciso y la descripción clara de mejorar la documentación oficial, dejar un comentario!
Otros consejos
Me encuentro a menudo la escritura de código a lo largo de este patrón cuando se trata de conmutatividad:
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