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

У меня есть код, похожий на этот, который я хотел бы улучшить:

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

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

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

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

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, но не исключаю обновления.Я бы посчитал ответ верным, если бы он тоже потребовал обновления.

1 Ответ

0 голосов
/ 25 ноября 2018

очень хороший вопрос.Я думаю, что ответ на ваш вопрос не получится, как вы могли ожидать.Решение, возможно, не хорошее, но работает.Прежде всего, вы не можете определить тип возврата доходности, кроме 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 должна работать с этим решением.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...