In PHP 5.5 we get a new keyword called yield. It is a language feature that allows us to create iterator style Generators in a very easy way by just defining a single function. Before PHP 5.5 you had two options when you wanted to iterate over a set of numbers or objects. You could either create an array with all the values stored in it. When you had a large number of values, this could easily consume much of your memory. The alternative was to implement the Iterator interface. This was tedious and error prone and often avoided, especially by more novice PHP developers. With yield, we now get a language feature that allows everyone to quickly create an iterator having to spend much time learning about iterators, what methods have to be implemented, and what these methods should do.

Usage of the yield keyword

The yield keyword work something like the return statement. You can yield a value which will be passed to the calling context. The difference between yield and return is that yield will not cause the function to exit. The function will continue running can yield any number of values. In the basic usage, the values yielded in this way can be used in a foreach loop. A function using yield to return values is called a Generator Function.

A simple example

Let’s consider the Collatz sequence, you start with an integer x. If it is even, you divide by two. If it is odd you replace it with 3*x+1. You repeat the process until you get to 1. The generator function looks like this

function collatz($val) {
  yield $val;

  while ($val != 1) {
    if ($val%2 == 0) $val /= 2;
    else $val = 3*$val + 1;

    yield $val;
  }
}

The function takes the starting value and returns successive elements of the number sequence, including the value passed to it. As you can see, the yield statement does not have to be inside a loop. You could even think of a generator function that just consists of a sequence of yield statements (although there would probably not be a good use for such a function). We can use the generator function in a foreach loop.

foreach (collatz(11) as $c) {
  echo $c," ";
}

This will print out the following numbers

11 34 17 52 26 13 40 20 10 5 16 8 4 2 1

The nice thing about generator functions is that you don’t need to know in advance how many elements you are going to produce. Changing the program above to call collatz(27) will produce a sequence of 112 numbers.

A little extra

We are not actually limited to using our generator function in foreach loops. We can make use of any function that takes an Iteratoror a Traversable as argument. Suppose we do want to fill an array with the values from our sequence. The easiest way to do this is by calling iterator_to_array().

$arr = iterator_to_array(collatz(11));

Transformations

I think one of the most powerful aspects of the new language feature is the ability to create transformations of other iterables. Consider the following simple example.

function multiply_sequence($a, $fac) {
  foreach ($a as $val) yield $val*$fac;
}

This function takes an array and a value and multiplies each element of the array with that value. Let’s see how this is used.

$arr = array(1,3,5);

foreach (multiply_sequence($arr,2) as $val) {
  echo $val," ";
}

This will write out the sequence

2 6 10

Note how the output values are calculated on the fly and not stored in any array. The original array remains unchanged. For a more practical example lets imagine you have an array of items that you want to present in an HTML list. How about this generator function.

function to_html_list($input) {
  foreach ($input as $val) {
    yield "<li>".$val."</li>";
  }
}

This will wrap all the list elements inside <li> and </li> tags. Using it is simple.

$arr = array("lorem","ipsum","dolor");

foreach (to_html_list($arr) as $val) {
  echo $val,"\n";
}

This will write out

<li>lorem</li>
<li>ipsum</li>
<li>dolor</li>

The possibilities are endless. You could write a generator function that takes an array of URL addresses and loads the corresponding content from the internet and passes it back for further processing.

Selections

One other possibility, related to transformations, are selections. Imagine you have an array of strings, but you only want to iterate over a subset of these strings that matches a given pattern. This can be easily achieved

function select_pattern($input, $pattern) {
  foreach ($input as $val) {
    if (preg_match($pattern, $val)) yield $val;
  }
}

This will create an iterator that selects only a subset of the input array.

Chaining Generator Functions

Once you have discovered the power of transformations, the logical next step is to chain generator functions together. In the explanations of the transformations above, I have sneakily suggested that the argument should be an array. But really we only need it to be a Traversable. This means we can pass the output of one generator function as an argument to the next and write code like this.

foreach (to_html_list(multiply_sequence(collatz(5),2)) as $val) {
  echo $val,"\n";
}

This will create a sequence using collatz(), multiply every value by 2 and then surround the value with HTML list tags. The output will be

<li>10</li>
<li>32</li>
<li>16</li>
<li>8</li>
<li>4</li>
<li>2</li>

Summary

The yield keyword is a powerful new tool that can open up completely new programming paradigms. It is possible to iterate over sequences that are generated on the fly and apply transformations to arrays without the need to create temporary arrays.

In an upcoming post I will talk about storing and using the Generator which is returned by the generator functions.

PHP 5.5: The Power to Yield
标签: