In Ruby, come fa coerce () in realtà il lavoro?
-
04-10-2019 - |
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 aFixnum
(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 è:
-
Chi invoca
point.coerce(3)
? E 'Rubino automaticamente, o si tratta di un codice all'interno del metodo di*
diFixnum
con la cattura un'eccezione? O è dalla dichiarazionecase
che quando non conosce uno dei tipi conosciuti, quindi chiamarecoerce
? -
non
coerce
sempre bisogno di restituire una matrice di 2 elementi? Può essere alcun array? Oppure può essere un array di 3 elementi? -
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 dacoerce
.) Chi lo fa? E 'fatto da Ruby o si fa dal codice inFixnum
? Se è fatto dal codice inFixnum
, allora si tratta di una "convenzione" che ognuno segue quando si fa un coercizione?Quindi, potrebbe essere il codice in
*
diFixnum
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
-
Così è davvero difficile aggiungere qualcosa al metodo di istanza
Fixnum
dicoerce
? Si ha già un sacco di codice in essa e non possiamo solo aggiungere alcune righe di valorizzarlo (ma abbiamo sempre voglia di?) -
Il
coerce
nella classePoint
è 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)
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:
-
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 vostroPoint#*
viene passato un argomento che non riconosce, si potrebbe chiedere che l'argomento acoerce
se stesso ad unPoint
chiamandoarg.coerce(self)
. -
Sì, deve essere una matrice di elementi 2, tale che
b_equiv, a_equiv = a.coerce(b)
-
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
-
L'idea è che non si deve modificare
Fixnum#*
. Se non sa cosa fare, ad esempio perché l'argomento è unaPoint
, allora vi chiederà chiamandoPoint#coerce
. -
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