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 in Fixnum 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:

  1. Wer ruft point.coerce(3)? Ist es Rubin automatisch, oder ist es ein Code innerhalb von * Methode von Fixnum durch eine Ausnahme zu kontrollieren? Oder ist es durch case Aussage, dass, wenn es nicht einer der bekannten Arten nicht kennt, dann coerce nennen?

  2. 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?

  3. 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 durch coerce zurückgegeben.) Wer macht es? Ist es von Ruby getan oder wird es durch Code in Fixnum getan? Wenn es durch Code in Fixnum getan wird, dann ist es eine „Konvention“, dass jeder folgt, wenn ein Zwang zu tun?

    So könnte es der Code in * von Fixnum 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
    
  4. So ist es wirklich schwer ist, etwas zu Fixnum die Instanzmethode coerce 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?)

  5. Die coerce in der Point 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)
    
War es hilfreich?

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:

  1. 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 Ihr Point#* ein Argument übergeben wird es nicht erkennt, würden Sie dieses Argument stellen sich durch den Aufruf coerce zu einem Point arg.coerce(self).

  2. Ja, es hat ein Array aus 2 Elementen sein, so dass b_equiv, a_equiv = a.coerce(b)

  3. 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
    
  4. Die Idee ist, dass Sie nicht Fixnum#* ändern sollten. Wenn es nicht weiß, was zum Beispiel zu tun, weil das Argument ein Point ist, dann wird es Sie fragen nach Point#coerce aufrufen.

  5. 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
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top