En Ruby, ¿cómo funciona coerce()?
coercion type-coercion (2)
Se dice que cuando tenemos un Point clase y sabemos cómo realizar el point * 3 la siguiente manera:
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:
Pointno puede ser coaccionado enFixnum(TypeError)
Entonces, necesitamos definir aún más una coerce método de instancia:
class Point
def coerce(something)
[self, something]
end
end
p 3 * point
Salida:
#<Point:0x3c45a88 @x=3, @y=6>
Entonces se dice que el 3 * point es lo mismo que 3.*(point) . Es decir, el método de instancia * toma un point argumento e invoca en el objeto 3 .
Ahora, dado que este método * no sabe cómo multiplicar un punto, entonces
point.coerce(3)
se llamará y obtendrá una matriz:
[point, 3]
y luego * se aplica una vez más a él, ¿es eso cierto?
Ahora, esto se comprende y ahora tenemos un nuevo objeto Point , como lo realiza el método de instancia * de la clase Point .
La pregunta es:
¿Quién invoca a
point.coerce(3)? ¿Es Ruby automáticamente, o es algún código dentro del*método deFixnummediante la captura de una excepción? ¿O es por declaración decaseque cuando no conoce uno de los tipos conocidos, llamacoerce?¿La
coercesiempre necesita devolver una matriz de 2 elementos? ¿No puede ser una matriz? ¿O puede ser una matriz de 3 elementos?¿Y es la regla que, el operador (o método)
*se invocará en el elemento 0, con el argumento del elemento 1? (El elemento 0 y el elemento 1 son los dos elementos en esa matriz devueltos porcoerce.) ¿Quién lo hace? ¿Lo hace Ruby o está hecho por código enFixnum? Si se hace por código enFixnum, entonces ¿es una "convención" que todos siguen al hacer una coacción?Entonces, ¿podría ser el código en
*deFixnumhaciendo 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 endEntonces, ¿es realmente difícil agregar algo a la
Fixnumdel método de instancia deFixnum? Ya tiene un montón de código y no podemos simplemente agregar algunas líneas para mejorarlo (¿pero alguna vez querremos?)La
coerceen la clasePointes bastante genérica y funciona con*o+porque son transitivos. ¿Qué pasa si no es transitivo, como si definiéramos que Point menos Fixnum es:point = Point.new(100,100) point - 20 #=> (80,80) 20 - point #=> (-80,-80)
Respuesta corta: echa un vistazo a cómo lo está haciendo Matrix .
La idea es que coerce devuelve [equivalent_something, equivalent_self] , donde equivalent_something es un objeto básicamente equivalente a something pero que sabe cómo hacer operaciones en su clase Point . En Matrix lib, construimos un Matrix::Scalar partir de cualquier objeto Numeric , y esa clase sabe cómo realizar operaciones en Matrix y Vector .
Para abordar sus puntos:
Sí, es Ruby directamente (verifique las llamadas a
rb_num_coerce_binen la fuente ), aunque sus propios tipos también deberían hacerlo si desea que su código sea extensible por otros. Por ejemplo, si suPoint#*es pasado un argumento que no reconoce, usted le pediría a ese argumento que secoercea unPointllamando aarg.coerce(self).Sí, tiene que ser una matriz de 2 elementos, tal que
b_equiv, a_equiv = a.coerce(b)Sí. Ruby lo hace para los tipos integrados, y también debería hacerlo en sus propios tipos personalizados si desea ser extensible:
def *(arg) if (arg is not recognized) self_equiv, arg_equiv = arg.coerce(self) self_equiv * arg_equiv end endLa idea es que no se debe modificar
Fixnum#*. Si no sabe qué hacer, por ejemplo, porque el argumento es unPoint, le preguntará llamando aPoint#coerce.La transitividad (o realmente la conmutatividad) no es necesaria, porque el operador siempre se llama en el orden correcto. Es solo el llamado a
coerceque revierte temporalmente el recibido y el argumento. No hay un mecanismo incorporado que asegure la conmutatividad de operadores como+,==, etc ...
Si alguien puede presentar una descripción concisa, precisa y clara para mejorar la documentación oficial, ¡deje un comentario!
A menudo me encuentro escribiendo 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