Domanda

Si dice che quando abbiamo un Point di classe e sa come eseguire point * 3 simile al seguente:

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

Output:

#<Point:0x336094 @x=1, @y=2>
#<Point:0x335fa4 @x=3, @y=6>

ma poi,

3 * point

Non si comprende:

  

Point non può essere costretto a Fixnum (TypeError)

Quindi abbiamo bisogno di definire ulteriormente un metodo di istanza coerce:

class Point
  def coerce(something)
    [self, something]
  end
end

p 3 * point

Output:

#<Point:0x3c45a88 @x=3, @y=6>

Quindi, non è detto che 3 * point è lo stesso di 3.*(point). Cioè, il metodo di istanza * prende un point argomento e richiamare sul 3 oggetto.

Ora, dal momento che questo metodo * non sa come moltiplicare un punto, quindi

point.coerce(3)

sarà chiamato, e tornare un array:

[point, 3]

e poi * è ancora una volta applicato ad esso, è vero?

Ora, questo è capito e ora abbiamo un nuovo oggetto Point, interpretato da metodo di istanza * della classe Point.

La domanda è:

  1. Chi invoca point.coerce(3)? E 'Rubino automaticamente, o si tratta di un codice all'interno del metodo di * di Fixnum con la cattura un'eccezione? O è dalla dichiarazione case che quando non conosce uno dei tipi conosciuti, quindi chiamare coerce?

  2. non coerce sempre bisogno di restituire una matrice di 2 elementi? Può essere alcun array? Oppure può essere un array di 3 elementi?

  3. Ed è la regola che, l'operatore originale (o metodo) * verrà poi richiamato sull'elemento 0, con l'argomento dell'elemento 1? (Elemento 0 e dell'elemento 1 sono i due elementi dell'array restituito da coerce.) Chi lo fa? E 'fatto da Ruby o si fa dal codice in Fixnum? Se è fatto dal codice in Fixnum, allora si tratta di una "convenzione" che ognuno segue quando si fa un coercizione?

    Quindi, potrebbe essere il codice in * di Fixnum fare qualcosa di simile:

    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. Così è davvero difficile aggiungere qualcosa al metodo di istanza Fixnum di coerce? Si ha già un sacco di codice in essa e non possiamo solo aggiungere alcune righe di valorizzarlo (ma abbiamo sempre voglia di?)

  5. Il coerce nella classe Point è abbastanza generico e funziona con * o + perché sono transitivi. E se non è transitiva, come ad esempio se definiamo punto negativo Fixnum di essere:

    point = Point.new(100,100)
    point - 20  #=> (80,80)
    20 - point  #=> (-80,-80)
    
È stato utile?

Soluzione

Risposta breve: controllare come Matrix sta facendo .

L'idea è che i rendimenti coerce [equivalent_something, equivalent_self], dove equivalent_something è un oggetto sostanzialmente equivalente a something ma che sa fare operazioni sul tua classe Point. Nel lib Matrix, viene costruito un Matrix::Scalar da qualsiasi oggetto Numeric, e quella classe sa come eseguire operazioni su Matrix e Vector.

Per risolvere i tuoi punti:

  1. Si, è Rubino direttamente (le chiamate di controllo a rb_num_coerce_bin nella fonte ), anche se i propri tipi dovrebbero fare anche se si desidera che il codice per essere estensibile da altri. Ad esempio se il vostro Point#* viene passato un argomento che non riconosce, si potrebbe chiedere che l'argomento a coerce se stesso ad un Point chiamando arg.coerce(self).

  2. Sì, deve essere una matrice di elementi 2, tale che b_equiv, a_equiv = a.coerce(b)

  3. Sì. Rubino lo fa per i tipi built-in, e si dovrebbe troppo sulle proprie tipi personalizzati, se si vuole essere estensibile:

    def *(arg)
      if (arg is not recognized)
        self_equiv, arg_equiv = arg.coerce(self)
        self_equiv * arg_equiv
      end
    end
    
  4. L'idea è che non si deve modificare Fixnum#*. Se non sa cosa fare, ad esempio perché l'argomento è una Point, allora vi chiederà chiamando Point#coerce.

  5. La transitività (o in realtà proprietà commutativa) non è necessario, perché l'operatore è sempre chiamato nel giusto ordine. E 'solo la chiamata a coerce che ritorna temporaneamente la ricevuta e l'argomento. Non v'è alcun meccanismo incorporato che assicura commutativity di operatori come +, ==, ecc ...

Se qualcuno può venire con un laconico, precisa e chiara descrizione di migliorare la documentazione ufficiale, lasciare un commento!

Altri suggerimenti

mi ritrovo spesso la scrittura di codice lungo questo modello quando si tratta di proprietà commutativa:

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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top