Как я могу реализовать типизированный / типобезопасный итератор?

У меня есть код, который выглядит следующим образом, который я хотел бы улучшить:

// 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;
}

Вещь, которую я хотел бы улучшить, — это аннотация в цикле (третья последняя строка), которую я хотел бы полностью удалить. Причины

  • это должно быть ненужным с правильными подсказками типов, которые даже применяются языком
  • он может отражать или не отражать реальность, т. е. подвержен изменениям кода
  • это ненужная работа, печатать и, что еще хуже, читать.

Мой наивный подход состоял в том, чтобы объявить интерфейс итератора, который добавляет правильную аннотацию типа к универсальному Iterator интерфейс:

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

Это имеет тот недостаток, что я не могу установить это как «жесткую» аннотацию для функции, только как аннотацию строки документации, потому что "Generators may only declare a return type of Generator, Iterator, Traversable, or iterable", что плохо, потому что тогда оно не соблюдается. Кроме того, моя среда IDE не выбирает тип, но это другая проблема.

Другой подход состоял в том, чтобы написать реальный класс итератора, который обернет Generator вернулся из функции. Проблема в том, что этот класс тоже должен быть создан, поэтому я должен был бы вызвать $stuffGenerator = new StuffIterator(searchStuff()); или написать другую функцию-обертку, чтобы сделать это, ни одна из которых не должна быть необходимой. Тем не менее, глупая IDE не принимает подсказку типа (grrrr …!).

Итак, вот мой вопрос: какие существуют альтернативы этому подходу? Я мог бы представить что-то вроде дженериков C ++ или Java, но, увы, я не могу просто переписать рассматриваемое приложение.

Дальнейшие заметки:

  • Пример кода работает, это не проблема, меня беспокоит скорее ремонтопригодность, удобочитаемость и элегантность.
  • Я не могу просто вернуть массив, использование генератора на этом этапе важно. Таким образом, любое предложение, основанное на этом подходе, не является решением.
  • Сейчас я использую PHP 7.1, но не исключаю обновления. Я бы посчитал ответ верным, если бы он тоже потребовал обновления.

3

Решение

очень хороший вопрос Я думаю, что ответ на ваш вопрос не получится, как вы могли ожидать. Решение, возможно, не хорошее, но работает. Прежде всего, вы не можете определить тип возврата доходности, кроме Generator и так далее. Вы дали ответ сами. Но …

Просто представьте следующую отправную точку.

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();
}
}

Что я здесь сделал? Мы знаем Stuff класс уже. Ничего нового Новая вещь это StuffCollection учебный класс. Из-за расширения его от IteratorIterator класс, который мы можем переопределить IteratorIterator::current() метод и дать ему подсказку типа.

$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>";
}

Выход из этого должен быть …

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)

Что это значит? Вы действительно не можете определить тип возвращаемого значения непосредственно в вызове yield. Доход всегда будет возвращать Generator пример. Одним из возможных решений может быть использование IteratorIterator учебный класс.

Даже ваша IDE должна работать с этим решением.

2

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]