ruby coercion coerce type-coercion

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:

Point no puede ser coaccionado en Fixnum ( 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:

  1. ¿Quién invoca a point.coerce(3) ? ¿Es Ruby automáticamente, o es algún código dentro del * método de Fixnum mediante la captura de una excepción? ¿O es por declaración de case que cuando no conoce uno de los tipos conocidos, llama coerce ?

  2. ¿La coerce siempre necesita devolver una matriz de 2 elementos? ¿No puede ser una matriz? ¿O puede ser una matriz de 3 elementos?

  3. ¿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 por coerce .) ¿Quién lo hace? ¿Lo hace Ruby o está hecho por código en Fixnum ? Si se hace por código en Fixnum , entonces ¿es una "convención" que todos siguen al hacer una coacción?

    Entonces, ¿podría ser el código en * de Fixnum haciendo 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

  4. Entonces, ¿es realmente difícil agregar algo a la Fixnum del método de instancia de Fixnum ? Ya tiene un montón de código y no podemos simplemente agregar algunas líneas para mejorarlo (¿pero alguna vez querremos?)

  5. La coerce en la clase Point es 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:

  1. Sí, es Ruby directamente (verifique las llamadas a rb_num_coerce_bin en la fuente ), aunque sus propios tipos también deberían hacerlo si desea que su código sea extensible por otros. Por ejemplo, si su Point#* es pasado un argumento que no reconoce, usted le pediría a ese argumento que se coerce a un Point llamando a arg.coerce(self) .

  2. Sí, tiene que ser una matriz de 2 elementos, tal que b_equiv, a_equiv = a.coerce(b)

  3. 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 end

  4. La idea es que no se debe modificar Fixnum#* . Si no sabe qué hacer, por ejemplo, porque el argumento es un Point , le preguntará llamando a Point#coerce .

  5. La transitividad (o realmente la conmutatividad) no es necesaria, porque el operador siempre se llama en el orden correcto. Es solo el llamado a coerce que 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