如何实现类型化/类型安全迭代器?

Ulr*_*rdt 5 php type-safety php-7

我的代码看起来像这样,我想改进它:\n \n\n

// example type\nclass Stuff\n{\n    public function __construct($name)\n    {\n        $this->name = $name;\n    }\n\n    public function getName()\n    {\n        return $this->name;\n    }\n}\n\n// generator function\nfunction searchStuff()\n{\n    yield new Stuff(\'Fou\');\n    yield new Stuff(\'Barre\');\n    yield new Stuff(\'Bazze\');\n}\n\n// code that iterates over the results of the generator\n$stuffIterator = searchStuff();\nassert($stuffIterator instanceof Iterator);\nforeach ($stuffIterator as $stuff) {\n    /** @var Stuff $stuff */\n    echo $stuff->getName() . PHP_EOL;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

我想要改进的是循环中的注释(倒数第三行),我想完全删除它。原因是

\n\n
    \n
  • 正确的类型提示应该是不必要的,甚至是由语言强制执行的
  • \n
  • 它可能反映也可能不反映现实,即它很容易因代码更改而中断
  • \n
  • 打字是不必要的工作,更糟糕的是阅读它。
  • \n
\n\n

我的 na\xc3\xafve 方法是声明一个迭代器接口,该接口向通用Iterator接口添加适当的类型注释:

\n\n
interface StuffIterator extends Iterator\n{\n    public function current(): Stuff;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

这有一个缺点,我不能将其设置为函数上的“硬”注释,只能作为文档字符串注释,因为"Generators may only declare a return type of Generator, Iterator, Traversable, or iterable",这是不好的,因为这样就不会强制执行。此外,我的 IDE 无法识别该类型,但这是一个不同的问题。

\n\n

另一种方法是编写一个实际的迭代器类来包装Generator函数的返回值。问题是这个类也需要实例化,所以我必须调用$stuffGenerator = new StuffIterator(searchStuff());或编写另一个包装函数来执行此操作,这两者都不是必需的。尽管如此,愚蠢的 IDE 仍然无法识别类型提示(grrrrr...!)。

\n\n

所以,这是我的问题:这种方法还有哪些替代方案?我想像 C++ 或 Java 泛型之类的东西,但可惜,我不能简单地重写有问题的应用程序。

\n\n

进一步说明:

\n\n
    \n
  • 示例代码有效,这不是问题,我关心的是可维护性、可读性和优雅性。
  • \n
  • 我不能简单地返回一个数组,此时使用生成器很重要。因此,任何基于这种方法的建议都不是解决方案。
  • \n
  • 我目前使用的是 PHP 7.1,但我不排除升级。如果它也需要升级,我会认为答案有效。
  • \n
\n

Mar*_*cel 2

这是一个很好的问题。我想你的问题的答案不会像你想象的那样。该解决方案可能不太好,但有效。首先,您不能定义除等之外的收益率返回类型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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我在这里做了什么?我们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>";
}
Run Code Online (Sandbox Code Playgroud)

其输出应该是......

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)
Run Code Online (Sandbox Code Playgroud)

这意味着什么?您确实不能直接在yield 调用中定义返回类型。yield 总是会返回一个Generator实例。一种可能的解决方案是使用IteratorIterator

即使您的 IDE 也应该适用于该解决方案。