How can I implement a typed/typesafe iterator?












3














I have code that looks like this, which I'd like to improve:



// example type
class Stuff
{
public function __construct($name)
{
$this->name = $name;
}

public function getName()
{
return $this->name;
}
}

// generator function
function searchStuff()
{
yield new Stuff('Fou');
yield new Stuff('Barre');
yield new Stuff('Bazze');
}

// code that iterates over the results of the generator
$stuffIterator = searchStuff();
assert($stuffIterator instanceof Iterator);
foreach ($stuffIterator as $stuff) {
/** @var Stuff $stuff */
echo $stuff->getName() . PHP_EOL;
}


The thing that I'd like to improve is the annotation in the loop (third last line), which I'd like to remove completely. The reasons are




  • it should be unnecessary with proper type hints that are even enforced by the language

  • it may or may not reflect reality, i.e. it is prone to break on code changes

  • it is unnecessary work typing it and, even worse, reading it.


My naïve approach was to declare an iterator interface that adds a proper type annotation to the generic Iterator interface:



interface StuffIterator extends Iterator
{
public function current(): Stuff;
}


This has the drawback that I can't set this as "hard" annotation on the function, only as docstring annotation, because "Generators may only declare a return type of Generator, Iterator, Traversable, or iterable", which is bad, because then it isn't enforced. Further, my IDE doesn't pick up the type, but that's a different issue.



A different approach was to write an actual iterator class that wraps the Generator returned from the function. Problem there is that this class needs to be instantiated as well, so I would have to call $stuffGenerator = new StuffIterator(searchStuff()); or write another wrapper function to do that, neither of which should be necessary. Still, the stupid IDE doesn't pick up the type hint (grrrr...!).



So, here's my question: What alternatives exist to this approach? I'd imagine something like C++ or Java generics, but alas, I can't simply rewrite the application in question.



Further notes:




  • The example code works, that's not the problem, my concerns are rather maintainability, readability and elegance.

  • I can't simply return an array, using a generator at this point is important. So, any suggestion based on this approach is not a solution.

  • I'm using PHP 7.1 at the moment, but I don't rule out upgrading. I'd consider an answer valid if it required upgrading, too.










share|improve this question



























    3














    I have code that looks like this, which I'd like to improve:



    // example type
    class Stuff
    {
    public function __construct($name)
    {
    $this->name = $name;
    }

    public function getName()
    {
    return $this->name;
    }
    }

    // generator function
    function searchStuff()
    {
    yield new Stuff('Fou');
    yield new Stuff('Barre');
    yield new Stuff('Bazze');
    }

    // code that iterates over the results of the generator
    $stuffIterator = searchStuff();
    assert($stuffIterator instanceof Iterator);
    foreach ($stuffIterator as $stuff) {
    /** @var Stuff $stuff */
    echo $stuff->getName() . PHP_EOL;
    }


    The thing that I'd like to improve is the annotation in the loop (third last line), which I'd like to remove completely. The reasons are




    • it should be unnecessary with proper type hints that are even enforced by the language

    • it may or may not reflect reality, i.e. it is prone to break on code changes

    • it is unnecessary work typing it and, even worse, reading it.


    My naïve approach was to declare an iterator interface that adds a proper type annotation to the generic Iterator interface:



    interface StuffIterator extends Iterator
    {
    public function current(): Stuff;
    }


    This has the drawback that I can't set this as "hard" annotation on the function, only as docstring annotation, because "Generators may only declare a return type of Generator, Iterator, Traversable, or iterable", which is bad, because then it isn't enforced. Further, my IDE doesn't pick up the type, but that's a different issue.



    A different approach was to write an actual iterator class that wraps the Generator returned from the function. Problem there is that this class needs to be instantiated as well, so I would have to call $stuffGenerator = new StuffIterator(searchStuff()); or write another wrapper function to do that, neither of which should be necessary. Still, the stupid IDE doesn't pick up the type hint (grrrr...!).



    So, here's my question: What alternatives exist to this approach? I'd imagine something like C++ or Java generics, but alas, I can't simply rewrite the application in question.



    Further notes:




    • The example code works, that's not the problem, my concerns are rather maintainability, readability and elegance.

    • I can't simply return an array, using a generator at this point is important. So, any suggestion based on this approach is not a solution.

    • I'm using PHP 7.1 at the moment, but I don't rule out upgrading. I'd consider an answer valid if it required upgrading, too.










    share|improve this question

























      3












      3








      3


      1





      I have code that looks like this, which I'd like to improve:



      // example type
      class Stuff
      {
      public function __construct($name)
      {
      $this->name = $name;
      }

      public function getName()
      {
      return $this->name;
      }
      }

      // generator function
      function searchStuff()
      {
      yield new Stuff('Fou');
      yield new Stuff('Barre');
      yield new Stuff('Bazze');
      }

      // code that iterates over the results of the generator
      $stuffIterator = searchStuff();
      assert($stuffIterator instanceof Iterator);
      foreach ($stuffIterator as $stuff) {
      /** @var Stuff $stuff */
      echo $stuff->getName() . PHP_EOL;
      }


      The thing that I'd like to improve is the annotation in the loop (third last line), which I'd like to remove completely. The reasons are




      • it should be unnecessary with proper type hints that are even enforced by the language

      • it may or may not reflect reality, i.e. it is prone to break on code changes

      • it is unnecessary work typing it and, even worse, reading it.


      My naïve approach was to declare an iterator interface that adds a proper type annotation to the generic Iterator interface:



      interface StuffIterator extends Iterator
      {
      public function current(): Stuff;
      }


      This has the drawback that I can't set this as "hard" annotation on the function, only as docstring annotation, because "Generators may only declare a return type of Generator, Iterator, Traversable, or iterable", which is bad, because then it isn't enforced. Further, my IDE doesn't pick up the type, but that's a different issue.



      A different approach was to write an actual iterator class that wraps the Generator returned from the function. Problem there is that this class needs to be instantiated as well, so I would have to call $stuffGenerator = new StuffIterator(searchStuff()); or write another wrapper function to do that, neither of which should be necessary. Still, the stupid IDE doesn't pick up the type hint (grrrr...!).



      So, here's my question: What alternatives exist to this approach? I'd imagine something like C++ or Java generics, but alas, I can't simply rewrite the application in question.



      Further notes:




      • The example code works, that's not the problem, my concerns are rather maintainability, readability and elegance.

      • I can't simply return an array, using a generator at this point is important. So, any suggestion based on this approach is not a solution.

      • I'm using PHP 7.1 at the moment, but I don't rule out upgrading. I'd consider an answer valid if it required upgrading, too.










      share|improve this question













      I have code that looks like this, which I'd like to improve:



      // example type
      class Stuff
      {
      public function __construct($name)
      {
      $this->name = $name;
      }

      public function getName()
      {
      return $this->name;
      }
      }

      // generator function
      function searchStuff()
      {
      yield new Stuff('Fou');
      yield new Stuff('Barre');
      yield new Stuff('Bazze');
      }

      // code that iterates over the results of the generator
      $stuffIterator = searchStuff();
      assert($stuffIterator instanceof Iterator);
      foreach ($stuffIterator as $stuff) {
      /** @var Stuff $stuff */
      echo $stuff->getName() . PHP_EOL;
      }


      The thing that I'd like to improve is the annotation in the loop (third last line), which I'd like to remove completely. The reasons are




      • it should be unnecessary with proper type hints that are even enforced by the language

      • it may or may not reflect reality, i.e. it is prone to break on code changes

      • it is unnecessary work typing it and, even worse, reading it.


      My naïve approach was to declare an iterator interface that adds a proper type annotation to the generic Iterator interface:



      interface StuffIterator extends Iterator
      {
      public function current(): Stuff;
      }


      This has the drawback that I can't set this as "hard" annotation on the function, only as docstring annotation, because "Generators may only declare a return type of Generator, Iterator, Traversable, or iterable", which is bad, because then it isn't enforced. Further, my IDE doesn't pick up the type, but that's a different issue.



      A different approach was to write an actual iterator class that wraps the Generator returned from the function. Problem there is that this class needs to be instantiated as well, so I would have to call $stuffGenerator = new StuffIterator(searchStuff()); or write another wrapper function to do that, neither of which should be necessary. Still, the stupid IDE doesn't pick up the type hint (grrrr...!).



      So, here's my question: What alternatives exist to this approach? I'd imagine something like C++ or Java generics, but alas, I can't simply rewrite the application in question.



      Further notes:




      • The example code works, that's not the problem, my concerns are rather maintainability, readability and elegance.

      • I can't simply return an array, using a generator at this point is important. So, any suggestion based on this approach is not a solution.

      • I'm using PHP 7.1 at the moment, but I don't rule out upgrading. I'd consider an answer valid if it required upgrading, too.







      php php-7 type-safety






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 20 at 19:38









      Ulrich Eckhardt

      361212




      361212
























          1 Answer
          1






          active

          oldest

          votes


















          2














          a very good question. I guess the answer to your question will not turn out as you might expect. The solution is perhaps not nice, but works. First of all you can not define a yield return type other than Generator and so on. You have given the answer yourself. But ...



          Just image the following starting point.



          class Stuff
          {
          protected $name;

          public function getName() : ?string
          {
          return $this->name;
          }

          public function setName(string $name) : Stuff
          {
          $this->name = $name;
          return $this;
          }
          }

          class StuffCollection extends IteratorIterator
          {
          public function __construct(Stuff ...$items)
          {
          parent::__construct(
          (function() use ($items) {
          yield from $items;
          })()
          );
          }

          public function current() : Stuff
          {
          return parent::current();
          }
          }


          What I 've done here? We know the Stuff class already. It does nothing new. The new thing is the StuffCollection class. Because of extending it from the IteratorIterator class we can override the IteratorIterator::current() method and give it a type hint.



          $collection = new StuffCollection(
          (new Stuff())->setName('One'),
          (new Stuff())->setName('Two'),
          (new Stuff())->setName('Three')
          );

          foreach ($collection as $item) {
          var_dump(assert($item instance of Stuff));
          echo sprintf(
          'Class: %s. Calling getName method returns "%s" (%s)',
          get_class($item),
          $item->getName(),
          gettype($item->getName())
          ) . "<br>";
          }


          The output from that should be ...



          bool(true) Class: Stuff. Calling getName method returns "One" (string)
          bool(true) Class: Stuff. Calling getName method returns "Two" (string)
          bool(true) Class: Stuff. Calling getName method returns "Three" (string)


          What does that mean? You really can not define the return type directly in a yield call. A yield will always return a Generator instance. One possible solution could be the use of the IteratorIterator class.



          Even your IDE should work with that solution.






          share|improve this answer



















          • 1




            Thanks, this is probably the best solution. My IDE doesn't pick up the type hint, but that's just its problem, maybe I need to upgrade finally. BTW: I mentioned I couldn't return an array, which you ignored. However, it's not even important, because I can wrap both a Generator or an array with your approach. In that case, the StuffIterator just extends the IteratorIterator baseclass and the only method it defines is current(), where it adds the type annotation and delegates to the baseclass.
            – Ulrich Eckhardt
            Dec 7 at 13:01











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53400354%2fhow-can-i-implement-a-typed-typesafe-iterator%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          2














          a very good question. I guess the answer to your question will not turn out as you might expect. The solution is perhaps not nice, but works. First of all you can not define a yield return type other than Generator and so on. You have given the answer yourself. But ...



          Just image the following starting point.



          class Stuff
          {
          protected $name;

          public function getName() : ?string
          {
          return $this->name;
          }

          public function setName(string $name) : Stuff
          {
          $this->name = $name;
          return $this;
          }
          }

          class StuffCollection extends IteratorIterator
          {
          public function __construct(Stuff ...$items)
          {
          parent::__construct(
          (function() use ($items) {
          yield from $items;
          })()
          );
          }

          public function current() : Stuff
          {
          return parent::current();
          }
          }


          What I 've done here? We know the Stuff class already. It does nothing new. The new thing is the StuffCollection class. Because of extending it from the IteratorIterator class we can override the IteratorIterator::current() method and give it a type hint.



          $collection = new StuffCollection(
          (new Stuff())->setName('One'),
          (new Stuff())->setName('Two'),
          (new Stuff())->setName('Three')
          );

          foreach ($collection as $item) {
          var_dump(assert($item instance of Stuff));
          echo sprintf(
          'Class: %s. Calling getName method returns "%s" (%s)',
          get_class($item),
          $item->getName(),
          gettype($item->getName())
          ) . "<br>";
          }


          The output from that should be ...



          bool(true) Class: Stuff. Calling getName method returns "One" (string)
          bool(true) Class: Stuff. Calling getName method returns "Two" (string)
          bool(true) Class: Stuff. Calling getName method returns "Three" (string)


          What does that mean? You really can not define the return type directly in a yield call. A yield will always return a Generator instance. One possible solution could be the use of the IteratorIterator class.



          Even your IDE should work with that solution.






          share|improve this answer



















          • 1




            Thanks, this is probably the best solution. My IDE doesn't pick up the type hint, but that's just its problem, maybe I need to upgrade finally. BTW: I mentioned I couldn't return an array, which you ignored. However, it's not even important, because I can wrap both a Generator or an array with your approach. In that case, the StuffIterator just extends the IteratorIterator baseclass and the only method it defines is current(), where it adds the type annotation and delegates to the baseclass.
            – Ulrich Eckhardt
            Dec 7 at 13:01
















          2














          a very good question. I guess the answer to your question will not turn out as you might expect. The solution is perhaps not nice, but works. First of all you can not define a yield return type other than Generator and so on. You have given the answer yourself. But ...



          Just image the following starting point.



          class Stuff
          {
          protected $name;

          public function getName() : ?string
          {
          return $this->name;
          }

          public function setName(string $name) : Stuff
          {
          $this->name = $name;
          return $this;
          }
          }

          class StuffCollection extends IteratorIterator
          {
          public function __construct(Stuff ...$items)
          {
          parent::__construct(
          (function() use ($items) {
          yield from $items;
          })()
          );
          }

          public function current() : Stuff
          {
          return parent::current();
          }
          }


          What I 've done here? We know the Stuff class already. It does nothing new. The new thing is the StuffCollection class. Because of extending it from the IteratorIterator class we can override the IteratorIterator::current() method and give it a type hint.



          $collection = new StuffCollection(
          (new Stuff())->setName('One'),
          (new Stuff())->setName('Two'),
          (new Stuff())->setName('Three')
          );

          foreach ($collection as $item) {
          var_dump(assert($item instance of Stuff));
          echo sprintf(
          'Class: %s. Calling getName method returns "%s" (%s)',
          get_class($item),
          $item->getName(),
          gettype($item->getName())
          ) . "<br>";
          }


          The output from that should be ...



          bool(true) Class: Stuff. Calling getName method returns "One" (string)
          bool(true) Class: Stuff. Calling getName method returns "Two" (string)
          bool(true) Class: Stuff. Calling getName method returns "Three" (string)


          What does that mean? You really can not define the return type directly in a yield call. A yield will always return a Generator instance. One possible solution could be the use of the IteratorIterator class.



          Even your IDE should work with that solution.






          share|improve this answer



















          • 1




            Thanks, this is probably the best solution. My IDE doesn't pick up the type hint, but that's just its problem, maybe I need to upgrade finally. BTW: I mentioned I couldn't return an array, which you ignored. However, it's not even important, because I can wrap both a Generator or an array with your approach. In that case, the StuffIterator just extends the IteratorIterator baseclass and the only method it defines is current(), where it adds the type annotation and delegates to the baseclass.
            – Ulrich Eckhardt
            Dec 7 at 13:01














          2












          2








          2






          a very good question. I guess the answer to your question will not turn out as you might expect. The solution is perhaps not nice, but works. First of all you can not define a yield return type other than Generator and so on. You have given the answer yourself. But ...



          Just image the following starting point.



          class Stuff
          {
          protected $name;

          public function getName() : ?string
          {
          return $this->name;
          }

          public function setName(string $name) : Stuff
          {
          $this->name = $name;
          return $this;
          }
          }

          class StuffCollection extends IteratorIterator
          {
          public function __construct(Stuff ...$items)
          {
          parent::__construct(
          (function() use ($items) {
          yield from $items;
          })()
          );
          }

          public function current() : Stuff
          {
          return parent::current();
          }
          }


          What I 've done here? We know the Stuff class already. It does nothing new. The new thing is the StuffCollection class. Because of extending it from the IteratorIterator class we can override the IteratorIterator::current() method and give it a type hint.



          $collection = new StuffCollection(
          (new Stuff())->setName('One'),
          (new Stuff())->setName('Two'),
          (new Stuff())->setName('Three')
          );

          foreach ($collection as $item) {
          var_dump(assert($item instance of Stuff));
          echo sprintf(
          'Class: %s. Calling getName method returns "%s" (%s)',
          get_class($item),
          $item->getName(),
          gettype($item->getName())
          ) . "<br>";
          }


          The output from that should be ...



          bool(true) Class: Stuff. Calling getName method returns "One" (string)
          bool(true) Class: Stuff. Calling getName method returns "Two" (string)
          bool(true) Class: Stuff. Calling getName method returns "Three" (string)


          What does that mean? You really can not define the return type directly in a yield call. A yield will always return a Generator instance. One possible solution could be the use of the IteratorIterator class.



          Even your IDE should work with that solution.






          share|improve this answer














          a very good question. I guess the answer to your question will not turn out as you might expect. The solution is perhaps not nice, but works. First of all you can not define a yield return type other than Generator and so on. You have given the answer yourself. But ...



          Just image the following starting point.



          class Stuff
          {
          protected $name;

          public function getName() : ?string
          {
          return $this->name;
          }

          public function setName(string $name) : Stuff
          {
          $this->name = $name;
          return $this;
          }
          }

          class StuffCollection extends IteratorIterator
          {
          public function __construct(Stuff ...$items)
          {
          parent::__construct(
          (function() use ($items) {
          yield from $items;
          })()
          );
          }

          public function current() : Stuff
          {
          return parent::current();
          }
          }


          What I 've done here? We know the Stuff class already. It does nothing new. The new thing is the StuffCollection class. Because of extending it from the IteratorIterator class we can override the IteratorIterator::current() method and give it a type hint.



          $collection = new StuffCollection(
          (new Stuff())->setName('One'),
          (new Stuff())->setName('Two'),
          (new Stuff())->setName('Three')
          );

          foreach ($collection as $item) {
          var_dump(assert($item instance of Stuff));
          echo sprintf(
          'Class: %s. Calling getName method returns "%s" (%s)',
          get_class($item),
          $item->getName(),
          gettype($item->getName())
          ) . "<br>";
          }


          The output from that should be ...



          bool(true) Class: Stuff. Calling getName method returns "One" (string)
          bool(true) Class: Stuff. Calling getName method returns "Two" (string)
          bool(true) Class: Stuff. Calling getName method returns "Three" (string)


          What does that mean? You really can not define the return type directly in a yield call. A yield will always return a Generator instance. One possible solution could be the use of the IteratorIterator class.



          Even your IDE should work with that solution.







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 25 at 17:09

























          answered Nov 25 at 17:03









          Marcel

          2,0351715




          2,0351715








          • 1




            Thanks, this is probably the best solution. My IDE doesn't pick up the type hint, but that's just its problem, maybe I need to upgrade finally. BTW: I mentioned I couldn't return an array, which you ignored. However, it's not even important, because I can wrap both a Generator or an array with your approach. In that case, the StuffIterator just extends the IteratorIterator baseclass and the only method it defines is current(), where it adds the type annotation and delegates to the baseclass.
            – Ulrich Eckhardt
            Dec 7 at 13:01














          • 1




            Thanks, this is probably the best solution. My IDE doesn't pick up the type hint, but that's just its problem, maybe I need to upgrade finally. BTW: I mentioned I couldn't return an array, which you ignored. However, it's not even important, because I can wrap both a Generator or an array with your approach. In that case, the StuffIterator just extends the IteratorIterator baseclass and the only method it defines is current(), where it adds the type annotation and delegates to the baseclass.
            – Ulrich Eckhardt
            Dec 7 at 13:01








          1




          1




          Thanks, this is probably the best solution. My IDE doesn't pick up the type hint, but that's just its problem, maybe I need to upgrade finally. BTW: I mentioned I couldn't return an array, which you ignored. However, it's not even important, because I can wrap both a Generator or an array with your approach. In that case, the StuffIterator just extends the IteratorIterator baseclass and the only method it defines is current(), where it adds the type annotation and delegates to the baseclass.
          – Ulrich Eckhardt
          Dec 7 at 13:01




          Thanks, this is probably the best solution. My IDE doesn't pick up the type hint, but that's just its problem, maybe I need to upgrade finally. BTW: I mentioned I couldn't return an array, which you ignored. However, it's not even important, because I can wrap both a Generator or an array with your approach. In that case, the StuffIterator just extends the IteratorIterator baseclass and the only method it defines is current(), where it adds the type annotation and delegates to the baseclass.
          – Ulrich Eckhardt
          Dec 7 at 13:01


















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53400354%2fhow-can-i-implement-a-typed-typesafe-iterator%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Wiesbaden

          Marschland

          Dieringhausen