In Ruby, wie funktioniert coerce () eigentlich?
-
04-10-2019 - |
Frage
Es wird gesagt, dass, wenn wir eine Klasse Point
haben und wissen, wie point * 3
wie folgt auszuführen:
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
Ausgabe:
#<Point:0x336094 @x=1, @y=2>
#<Point:0x335fa4 @x=3, @y=6>
aber dann,
3 * point
nicht verstanden wird:
Point
kann nicht inFixnum
umgewandelt werden (TypeError
)
So müssen wir weiter eine Instanz Methode coerce
definieren:
class Point
def coerce(something)
[self, something]
end
end
p 3 * point
Ausgabe:
#<Point:0x3c45a88 @x=3, @y=6>
So wird gesagt, dass 3 * point
die gleiche wie 3.*(point)
ist. Das heißt, die Instanz-Methode *
ein Argument point
und invoke auf dem Objekt 3
.
Nun, da diese Methode *
nicht weiß, wie um einen Punkt zu multiplizieren, so
point.coerce(3)
wird aufgerufen, und ein Array zurück:
[point, 3]
und dann wird *
erneut darauf angewendet wird, das wahr?
Nun, dies zu verstehen ist und wir jetzt einen neuen Point
Gegenstand haben, wie sie in der Instanzmethode *
der Point
Klasse durchgeführt.
Die Frage ist:
-
Wer ruft
point.coerce(3)
? Ist es Rubin automatisch, oder ist es ein Code innerhalb von*
Methode vonFixnum
durch eine Ausnahme zu kontrollieren? Oder ist es durchcase
Aussage, dass, wenn es nicht einer der bekannten Arten nicht kennt, danncoerce
nennen? -
Does
coerce
immer brauchen ein Array von 2 Elementen zurück? Kann es keinen Array sein? Oder kann es eine Anordnung von 3 Elementen sein? -
Und die Regel ist, dass der ursprüngliche Betreiber (oder Methode)
*
wird dann auf Element aufgerufen werden 0, mit dem Argument des Elements 1? (Element 0 und Element 1 sind die beiden Elemente in dieser Anordnung durchcoerce
zurückgegeben.) Wer macht es? Ist es von Ruby getan oder wird es durch Code inFixnum
getan? Wenn es durch Code inFixnum
getan wird, dann ist es eine „Konvention“, dass jeder folgt, wenn ein Zwang zu tun?So könnte es der Code in
*
vonFixnum
so etwas wie dies zu tun: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
-
So ist es wirklich schwer ist, etwas zu
Fixnum
die Instanzmethodecoerce
hinzufügen? Es hat bereits eine Menge Code in ihm und wir können nicht nur ein paar Zeilen hinzufügen, um es zu verbessern (aber werden wir jemals wollen?) -
Die
coerce
in derPoint
Klasse ist recht allgemein und es funktioniert mit*
oder+
weil sie transitiv sind. Was passiert, wenn es nicht transitiv ist, wie wenn wir definieren Punkt minus Fixnum sein:point = Point.new(100,100) point - 20 #=> (80,80) 20 - point #=> (-80,-80)
Lösung
Kurze Antwort: Besuchen wie Matrix
tut es .
Die Idee ist, dass coerce
kehrt [equivalent_something, equivalent_self]
, wo equivalent_something
ist ein Objekt im Grunde gleichwertig something
aber die weiß, wie Operationen auf Ihrer Point
Klasse zu tun. Im Matrix
lib konstruieren wir eine Matrix::Scalar
von jedem Numeric
Objekt, und diese Klasse weiß, wie Operationen auf Matrix
und Vector
auszuführen.
Ihre Punkte ansprechen:
-
Ja, es ist Ruby-direkt (Scheck Anrufe an
rb_num_coerce_bin
in der Quelle ), obwohl Ihre eigene Art tun zu müssen, wenn Sie Ihren Code von anderen sein erweiterbar wollen. Zum Beispiel, wenn IhrPoint#*
ein Argument übergeben wird es nicht erkennt, würden Sie dieses Argument stellen sich durch den Aufrufcoerce
zu einemPoint
arg.coerce(self)
. -
Ja, es hat ein Array aus 2 Elementen sein, so dass
b_equiv, a_equiv = a.coerce(b)
-
Ja. Rubin macht es für eingebaute Typen, und Sie sollten auf eigene benutzerdefinierte Typen auch, wenn Sie wollen, erweiterbar sein:
def *(arg) if (arg is not recognized) self_equiv, arg_equiv = arg.coerce(self) self_equiv * arg_equiv end end
-
Die Idee ist, dass Sie nicht
Fixnum#*
ändern sollten. Wenn es nicht weiß, was zum Beispiel zu tun, weil das Argument einPoint
ist, dann wird es Sie fragen nachPoint#coerce
aufrufen. -
Transitivity (oder eigentlich commutativity) ist nicht erforderlich, da der Bediener immer in der richtigen Reihenfolge aufgerufen wird. Es ist nur der Aufruf an
coerce
, die vorübergehend das empfangene und das Argument zurückkehrt. Es gibt keinen eingebauten Mechanismus, den commutativity von Operatoren wie+
versichert,==
, etc ...
Wenn jemand mit einem lapidaren kommen kann, präzise und klare Beschreibung der offiziellen Dokumentation zu verbessern, lassen Sie einen Kommentar!
Andere Tipps
Ich finde mich oft Code entlang dieses Muster zu schreiben, wenn sie mit commutativity Umgang:
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