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 a Fixnum (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:

  1. 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ón case que cuando no sabe uno de los tipos conocidos, a continuación, llamar coerce?

  2. ¿El coerce siempre hay que devolver una matriz de 2 elementos? ¿Puede ser ninguna matriz? O puede ser una matriz de 3 elementos?

  3. 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 por coerce.) ¿Quién lo hace? ¿Se hace por Rubí o está hecho por código en Fixnum? Si se hace por código en Fixnum, entonces es una "convención" que todo el mundo sigue cuando se hace una coacción?

    Así podría ser el código en * de Fixnum 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
    
  4. Por lo tanto, es muy difícil añadir algo al método de instancia Fixnum de coerce? 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?)

  5. El coerce en la clase Point 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)
    
¿Fue útil?

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:

  1. 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 su Point#* se pasa un argumento que no reconoce, podría pedir que argumento para coerce sí a un Point llamando arg.coerce(self).

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

  3. 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
    
  4. La idea es que no se debe modificar Fixnum#*. Si no sabe qué hacer, por ejemplo, porque el argumento es una Point, a continuación, se le pedirá que llamando Point#coerce.

  5. 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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top