PHP Generators

Overview

生成器提供了一种简易的方式来实现简单的迭代器,生成器就是简单的迭代器,相比较定义类实现Iterator接口的方式,其性能开销和复杂性大大降低。

一个生成器可以让你直接用foreach()迭代其中的一组数据。可以写一个生成器函数,和普通函数只返回一次不同的是,生成器可以根据需要产生(yield)多次返回。

EXAMPLE#1:使用生成器重新实现range()函数

function xrange($start, $limit, $step = 1) {
    if ($start < $limit) {
        if ($step < 0) {
            throw new Exception("Step must be +ve");
        }

        for($i = $start; $i <= $limit; $i += $step) {
            yield $i;
        }
    } else {
        if ($step >= 0) {
            throw new Exception("Step must be -ve");

        }

        for($i = $start; $i >= $limit; $i += $step) {
            yield $i;
        }
    }
}

foreach (range(1, 9, 2) as $key => $value) {
    echo ":$value";
}
echo "\n";
foreach (xrange(1, 9, 2) as $key => $value) {
    echo ":$value";
}
echo "\n";

// output
//:1:3:5:7:9
//:1:3:5:7:9

yield 关键字

生成器函数和普通函数的区别在于返回值,生成器函数可以yield生成多个值,而普通函数只能返回一个值。

当生成器函数被调用时,它返回一个可以被迭代的对象。当你遍历那个对象(比如foreach()),PHP每次需要一个值时就会调用生成器函数,然后保存生成器状态,以便于返回下次的值。当没有值可以返回时,生成器函数就退出了,然后外部调用继续执行,就好像遍历完了一个数组一样。

生成器函数的核心就是yield关键字。它最简单的形式看起来就行一个return语句,所不同的是,普通return语句会返回值并终止函数的运行,而yield会返回一个值给循环调用此生成器的代码,并且暂停执行生成器函数。

<?php
function gen_one_to_three() {
    for ($i = 1; $i <= 3; $i++) {
        yield $i;
    }
}
$generator = gen_one_to_three();
foreach ($generator as $value) {
    echo "$value ";
}

yield 值及其键

yield键值对类似于定义个关联数组,如下例:

$input = <<<'EOD'
1;PHP;Likes dollar signs
2;Python;Likes whitespace
3;Ruby;Likes blocks
EOD;

function input_parser($input) {
    foreach (explode("\n", $input) as $line) {
        $fields = explode(';', $line);
        $id = array_⇧($fields);

        yield $id => $fields;
    }
}

foreach (input_parser($input) as $id => $fields) {
    echo "$id:\n";
    echo "    $fields[0]\n";
    echo "    $fields[1]\n";
}

对于这个例子,我的理解就是input_parser会生成并返回一个关联数组,但是它不会立即返回完整的关联数组,而是按需生成关联数组中某一项的值。

yield引用

// TODO

生成器和Iterator对象的比较

生成器最大的优点就是简洁、灵活,当然这也是有代价的:生成器只能向后迭代,不能回到开始的位置

一些例子

function getFibonacci () {
    $i = 0;
    $k = 1;
    yield $k;
    while (true) {
        $k = $i + $k;
        $i = $k - $i;
        yield $k;
    }
}
$y = 0;
foreach (getFibonacci() as $fibonacci) {
    echo $fibonacci . "\n";
    $y++;
    if ($y > 30) {
        break;
    }
}