php yield

时间:2015-06-06 14:52:21   收藏:0   阅读:4792

yield特性一般跟生成器generator紧密联系在一起

Generator implements Iterator {
/* Methods */
public mixed current ( void )
public mixed key ( void )
public void next ( void )
public void rewind ( void )
public mixed send ( mixed $value )
public mixed throw ( Exception $exception )
public bool valid ( void )
public void __wakeup ( void )

}

注释:send()方法主要用于发送数据给当前yield,即yield被当作一个值被替换,且继续执行下一个yield,相当于next加current

所谓Generators, 我们以下称为”生成器”, 是一种可以返回迭代器的生成器. 呵呵, 这话有点绕, 让我们看看一个代码, 在没有迭代器之前, 如果我们遍历一个动态生成的数组:

<?php

   function return_array() {

      $array = dummy(); //计算全部数组内容

      return $array;

  }

foreach (return_array() as $v) {}

这里就有一个问题, 我们需要一次性生成全部数组内容, 并且返回, 想象一下如果数据来源非常大, 我们无法一次性读入内存.

当然, 我们可以采用一个类, 封装一个支持迭代的实现:

<?php

  class dummy implements Iterator {

       public function rewind() {

      //实现代码

      }

      public function valid() {

      //实现代码

      }

    public function current() {

      //实现代码

    }

    public function key() {

    //实现代码

    }

    public function next() {

    //实现代码

    }

}

foreach (new Dummy() as $v) {}

相比这种实现, 生成器提供了一种更加简便的选择, 比如实现如上同样的功能:

<?php

function genrators() {

   while ($i = dummy_line()) //生成数组的一个元素 

  {

          yield $i;

   }

}

foreach (generators() as $v) {}

也就是说, 每当产生一个数组元素, 就通过yield关键字返回成一个, 并且函数执行暂停, 当返回的迭代器的next方法被调用的时候, 会恢复刚才函数的执行, 从上一次被yield暂停的位置开始继续执行, 到下一次遇到yield的时候, 再次返回.

迭代生成器

(迭代)生成器也是一个函数,不同的是这个函数的返回值是依次返回,而不是只返回一个单独的值.或者,换句话说,生成器使你能更方便的实现了迭代器接口.下面通过实现一个xrange函数来简单说明:

<?php

function xrange($start, $end, $step = 1) {

    for ($i = $start; $i <= $end; $i += $step) {

        yield $i;

    }

}

foreach (xrange(1, 1000000) as $num) {

   echo $num, "\n";

}

上面这个xrange()函数提供了和PHP的内建函数range()一样的功能.但是不同的是range()函数返回的是一个包含值从1到100万0的数组(注:请查看手册). 而xrange()函数返回的是依次输出这些值的一个迭代器, 而不会真正以数组形式返回.

这种方法的优点是显而易见的.它可以让你在处理大数据集合的时候不用一次性的加载到内存中.甚至你可以处理无限大的数据流.

当然,也可以不同通过生成器来实现这个功能,而是可以通过继承Iterator接口实现.但通过使用生成器实现起来会更方便,不用再去实现iterator接口中的5个方法了.

生成器为可中断的函数

要从生成器认识协程, 理解它内部是如何工作是非常重要的: 生成器是一种可中断的函数, 在它里面的yield构成了中断点.

还是看上面的例子, 调用xrange(1,1000000)的时候, xrange()函数里代码其实并没有真正地运行. 它只是返回了一个迭代器:

<?php

$range = xrange(1, 1000000);

var_dump($range); // object(Generator)#1

var_dump($range instanceof Iterator); // bool(true)

?>

这也解释了为什么xrange叫做迭代生成器, 因为它返回一个迭代器, 而这个迭代器实现了Iterator接口.

调用迭代器的方法一次, 其中的代码运行一次.例如, 如果你调用$range->rewind(), 那么xrange()里的代码就会运行到控制流第一次出现yield的地方. 而函数内传递给yield语句的返回值可以通过$range->current()获取.

为了继续执行生成器中yield后的代码, 你就需要调用$range->next()方法. 这将再次启动生成器, 直到下一次yield语句出现. 因此,连续调用next()和current()方法, 你就能从生成器里获得所有的值, 直到再没有yield语句出现.

对xrange()来说, 这种情形出现在$i超过$end时. 在这中情况下, 控制流将到达函数的终点,因此将不执行任何代码.一旦这种情况发生,vaild()方法将返回假, 这时迭代结束.

协程

协程的支持是在迭代生成器的基础上, 增加了可以回送数据给生成器的功能(调用者发送数据给被调用的生成器函数). 这就把生成器到调用者的单向通信转变为两者之间的双向通信.

传递数据的功能是通过迭代器的send()方法实现的. 下面的logger()协程是这种通信如何运行的例子:

<?php

function logger($fileName) {

    $fileHandle = fopen($fileName, ‘a‘);

    while (true) {

    fwrite($fileHandle, yield . "\n");

    }

}

$logger = logger(__DIR__ . ‘/log‘);

$logger->send(‘Foo‘);

$logger->send(‘Bar‘)

?>

正如你能看到,这儿yield没有作为一个语句来使用, 而是用作一个表达式, 即它能被演化成一个值. 这个值就是调用者传递给send()方法的值. 在这个例子里, yield表达式将首先被”Foo”替代写入Log, 然后被”Bar”替代写入Log.

上面的例子里演示了yield作为接受者, 接下来我们看如何同时进行接收和发送的例子:

<?php

function gen() {

    $ret = (yield ‘yield1‘);

    var_dump($ret);

    $ret = (yield ‘yield2‘);

    var_dump($ret);

}

$gen = gen();

var_dump($gen->current()); // string(6) "yield1"

var_dump($gen->send(‘ret1‘)); // string(4) "ret1" (the first var_dump in gen)

// string(6) "yield2" (the var_dump of the ->send() return value)

var_dump($gen->send(‘ret2‘)); // string(4) "ret2" (again from within gen)

// NULL (the return value of ->send())

?>

要很快的理解输出的精确顺序可能稍微有点困难, 但你确定要搞清楚为什按照这种方式输出. 以便后续继续阅读.

另外, 我要特别指出的有两点:

第一点,yield表达式两边的括号在PHP7以前不是可选的, 也就是说在PHP5.5和PHP5.6中圆括号是必须的.

第二点,你可能已经注意到调用current()之前没有调用rewind().这是因为生成迭代对象的时候已经隐含地执行了rewind操作.

评论(0
© 2014 mamicode.com 版权所有 京ICP备13008772号-2  联系我们:gaon5@hotmail.com
迷上了代码!