Rolf B: Warum muss ich einen CallbackFilterIterator rewind()-en bevor ich ihn nutzen kann?

problematische Seite

Hallo alle,

ich wollte in PHP die eingebauten Iteratoren ausprobieren, z.B. den CallbackFilterIterator. Der bekommt dem Vernehmen nach einen Iterator als Parameter und einen Callback.

Der CallbackFilterIterator iteriert dann über den übergebenen Iterator, ruft pro erhaltenem Wert den Callback auf, und wenn der callback TRUE sagt, wird der Wert geyieldet.

Als Input habe ich mir einen einfachen Generator gebaut, der Zahlen eines Intervalls liefert, und wollte daraus die durch 3 teilbaren Zahlen herausfischen.

function Zahlenbereich($von, $bis)
{
   for ($i = $von; $i <= $bis; $i++)
      yield $i;
}

$zb_1_10 = Zahlenbereich(1,10);

$by3 = CallbackFilterIterator($zb_1_10, fn($z) => $z % 3 == 0);
foreach ($by3 as $z)
   echo "$z\n";

Doch, doch, das ist PHP. Das funktioniert soweit. Ich bekommt 3, 6 und 9.

Aber wenn ich $by3 manuell iterieren will, passiert nichts:

while ($by3->valid()) {
   echo $by3->current() . "\n";
   $by3->next();
}

Ich muss vor der Schleife $by3->rewind() aufrufen.

Das ist nicht der Fall, wenn ich mit dieser Schleife den einfachen Generator $zb_1_10 iterieren will. Ich habe die Doku so verstanden, dass foreach implizit einen rewind ausführt und deshalb kein Problem hat.

Wieso muss ich den CallbackFilterGenerator manuell rewinden? Das macht den Gebrauch ziemlich lästig. Es ist auch nicht intuitiv, vor allem, weil man Generatoren auch gar nicht rewinden kann. Ist das ein PHP Bug?

Rolf

--
sumpsi - posui - obstruxi

akzeptierte Antworten

  1. problematische Seite

    Tach!

    Wieso muss ich den CallbackFilterGenerator manuell rewinden?

    Du meinst CallbackFilterIterator. Ein Iterator ist ein allgemeines Interface. Das gab es schon lange bevor Generatoren ins Spiel kamen. Es ist auch nicht erforderlich, dass ein Iterator intern einen Generator verwendet. Sattdessen kann er die Methoden auch herkömmlich implementieren. Und dazu gehört dann auch, einen definierten Anfangszustand herstellen zu können, was man mit rewind() macht.

    Das macht den Gebrauch ziemlich lästig.

    Bevor es foreach gab, hat man Arrays mit while durchlaufen müssen. Es gibt einen internen Array-Pointer/-Cursor, den man mit next() oder auch each() zum nächsten Element befördert hat. Den hat man auch zurücksetzen/initialisieren müssen, wozu es reset() gibt.

    Es ist auch nicht intuitiv, vor allem, weil man Generatoren auch gar nicht rewinden kann. Ist das ein PHP Bug?

    Ein Generator ist die Vereinfachung eines Iterators. Ein Generator ist eine einzelne Funktion. Er hat damit einen definierten Startpunkt. Die Funktion kann das, was ein Iterator im rewind() ausführen muss, am Anfang ausführen, bevor das erste yield aufgerufen wird. Deshalb ist da kein separates "Rewinden" notwendig.

    dedlfix.

    1. problematische Seite

      Hallo dedlfix,

      Du meinst CallbackFilterIterator.

      Ach verdammt. Natürlich.

      Es ist auch nicht erforderlich, dass ein Iterator intern einen Generator verwendet.

      Schon klar. Ich wollte halt was schnelles einfaches haben, was iterierbar ist und als Input für den CallbackFilterIterator geeignet ist. Die Beispiele im PHP Handbuch verwenden den FilesystemIterator, den kann ich in der PHP Sandbox nicht verwenden.

      Aber diese Beispiele verwenden keinen rewind für den CallbackFilterIterator. Deswegen bin ich verwirrt, dass ich für meinen Spielzeugfall einen brauchen sollte.

      Ich habe jetzt mal das hier gemacht:

      $aa = new ArrayIterator([1,2,3,4,5,6,7,8,9,10]);
      if ($aa->valid()) echo "ArrayIterator is valid";
      $fa = new CallbackFilterIterator($aa, fn($i) => $i % 3 == 0);
      
      if ($aa->valid()) echo "ArrayIterator is valid";
      if ($fa->valid()) echo "CbFilterIterator is valid";
      

      Der ArrayIterator ist nach Erzeugung valid, ohne rewind. Nach Erstellung des CallbackFilterIterators ist der ArrayIterator immer noch valid. Aber der CallbackFilterIterator erst nach rewind.

      Zwei SPL Iteratoren sollten soch doch gleich verhalten. Doch ein Bug?

      Rolf

      --
      sumpsi - posui - obstruxi
      1. problematische Seite

        Tach!

        Der ArrayIterator ist nach Erzeugung valid, ohne rewind. Nach Erstellung des CallbackFilterIterators ist der ArrayIterator immer noch valid. Aber der CallbackFilterIterator erst nach rewind.

        Das kann ja sein, dass der Array-Iterator-Constructor die Default-Werte schon passend setzt oder sie so vordefiniert sind. Gab es eine Garantie, dass das so sei?

        Das rewind() muss jedenfalls zu Beginn ausgeführt werden. So ist es vorgesehen. Wenn dabei nichts passiert, weil die Default-Werte bereits so sind, dann ist das eher so zu betrachten, wie eine gewünschte Sortierung einer Datenbank-Abfrage ohne ORDER BY. Passt zufällig, ist aber nicht garantiert.

        Zwei SPL Iteratoren sollten soch doch gleich verhalten. Doch ein Bug?

        Seh ich nicht so. Sie verhalten sich ja gleich, wenn du das rewind() zur Initialisierung ausführst. Das gehört dazu, wenn man next() zu Fuß verwendet.

        dedlfix.

        1. problematische Seite

          Hallo dedlfix,

          So ist es vorgesehen.

          Hm. Aber nicht klar dokumentiert. Nach einiger Suche finde ich den PHP Bugs unter Bug 63823

          [2013-02-05 17:34 UTC] levim@php.net
          By design, iterators are required to call rewind before using them.

          Aber das finde ich nicht im Handbuch, dabei sollte das doch irgendwo prominent geschrieben stehen. Das habe ich jetzt man als Manual-Bug aufgemacht, mal gucken was draus wird…

          Danke.

          Rolf

          --
          sumpsi - posui - obstruxi