How can I implement a typed/typesafe iterator?
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
add a comment |
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
add a comment |
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
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
php php-7 type-safety
asked Nov 20 at 19:38
Ulrich Eckhardt
361212
361212
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
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.
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 aGenerator
or an array with your approach. In that case, theStuffIterator
just extends theIteratorIterator
baseclass and the only method it defines iscurrent()
, where it adds the type annotation and delegates to the baseclass.
– Ulrich Eckhardt
Dec 7 at 13:01
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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.
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 aGenerator
or an array with your approach. In that case, theStuffIterator
just extends theIteratorIterator
baseclass and the only method it defines iscurrent()
, where it adds the type annotation and delegates to the baseclass.
– Ulrich Eckhardt
Dec 7 at 13:01
add a comment |
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.
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 aGenerator
or an array with your approach. In that case, theStuffIterator
just extends theIteratorIterator
baseclass and the only method it defines iscurrent()
, where it adds the type annotation and delegates to the baseclass.
– Ulrich Eckhardt
Dec 7 at 13:01
add a comment |
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.
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.
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 aGenerator
or an array with your approach. In that case, theStuffIterator
just extends theIteratorIterator
baseclass and the only method it defines iscurrent()
, where it adds the type annotation and delegates to the baseclass.
– Ulrich Eckhardt
Dec 7 at 13:01
add a comment |
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 aGenerator
or an array with your approach. In that case, theStuffIterator
just extends theIteratorIterator
baseclass and the only method it defines iscurrent()
, 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
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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