Industrial Cooperation to Electrify Africa - Africa Energy Forum
Electrify your code with PHP Generators
-
Upload
mark-baker -
Category
Software
-
view
338 -
download
1
Transcript of Electrify your code with PHP Generators
Electrify your Code with
PHP Generators
PHP Generators
Wikipedia defines a Generator as:
A generator is very similar to a function that returns an array, in that a generator has parameters, can be called, and generates a sequence of values. However, instead of building an array containing all the values and returning them all at once, a generator yields the values one at a time, which requires less memory and allows the caller to get started processing the first few values immediately. In short, a generator looks like a function but behaves like an iterator.
PHP Generators
• Introduced in PHP 5.5• Iterable (Traversable) Objects• Can return a series of values, one at a time• Maintain state between iterations• Can accept values when sent to the generator• Similar to enumerators in Ruby, or Sequence Expressions in F#
PHP Generators
• Don’t • Add anything to PHP that couldn’t be done before
• Do• Allow you to perform iterative operations without an array to iterate• Potentially reduce memory use• Potentially faster than iterating over an array• Can be type-hinted in function/method definitions• Potentially cleaner and shorter code• Add semantics to your code
PHP Generators
• Automatically created when PHP identifies a function or method containing the “yield” keyword
function myGenerator() { yield 1; }
$generator = myGenerator(); var_dump($generator);
object(Generator)#1 (0) { }
PHP Generators
• Implemented as an Object
final class Generator implements Iterator { mixed current( void ); mixed key( void ); void next( void ); void rewind( void ); mixed send( mixed $value ); mixed throw( Exception $exception ); bool valid( void ); public void __wakeup ( void )}
• Can’t be extended
PHP Generators
function xrange($lower, $upper) { for ($i = $lower; $i <= $upper; ++$i) { yield $i; } }
$rangeGenerator = xrange(0,10);
while ($rangeGenerator->valid()) { $key = $rangeGenerator->key(); $value = $rangeGenerator->current(); echo $key , ' -> ' , $value, PHP_EOL; $rangeGenerator->next(); }
PHP Generators
function xrange($lower, $upper) { for ($i = $lower; $i <= $upper; ++$i) { yield $i; } }
foreach (xrange(0,10) as $key => $value) { echo $key , ' -> ' , $value, PHP_EOL; }
PHP Generators
foreach (range(0,65535) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL;}
function xrange($lower, $upper) { for ($i = $lower; $i <= $upper; ++$i) { yield $i; }}
foreach (xrange(0, 65535) as $i => $value) { echo $i , ' -> ' , $value, PHP_EOL;}
for($i = 0; $i <= 65535; ++$i) { echo $i , ' -> ' , $value, PHP_EOL;}
Time: 0.0183 s
Current Memory: 123.44 k
Peak Memory: 5500.11 k
Time: 0.0135 s
Current Memory: 124.33 k
Peak Memory: 126.84 k
Time: 0.0042 s
Current Memory: 122.92 k
Peak Memory: 124.49 k
PHP Generators
function xlColumnRange($lower, $upper) {
++$upper;
for ($i = $lower; $i != $upper; ++$i) {
yield $i;
}
}
foreach (xlColumnRange('A', 'CQ') as $i =>
$value) {
printf('%3d -> %2s', $i, $value);
echo (($i > 0) && ($i+1 % 5 == 0)) ?
PHP_EOL :
"\t";
}
0 -> A 1 -> B 2 -> C 3 -> D 4 -> E
5 -> F 6 -> G 7 -> H 8 -> I 9 -> J
10 -> K 11 -> L 12 -> M 13 -> N 14 -> O
15 -> P 16 -> Q 17 -> R 18 -> S 19 -> T
20 -> U 21 -> V 22 -> W 23 -> X 24 -> Y
25 -> Z 26 -> AA 27 -> AB 28 -> AC 29 -> AD
30 -> AE 31 -> AF 32 -> AG 33 -> AH 34 -> AI
35 -> AJ 36 -> AK 37 -> AL 38 -> AM 39 -> AN
40 -> AO 41 -> AP 42 -> AQ 43 -> AR 44 -> AS
45 -> AT 46 -> AU 47 -> AV 48 -> AW 49 -> AX
50 -> AY 51 -> AZ 52 -> BA 53 -> BB 54 -> BC
55 -> BD 56 -> BE 57 -> BF 58 -> BG 59 -> BH
60 -> BI 61 -> BJ 62 -> BK 63 -> BL 64 -> BM
65 -> BN 66 -> BO 67 -> BP 68 -> BQ 69 -> BR
70 -> BS 71 -> BT 72 -> BU 73 -> BV 74 -> BW
75 -> BX 76 -> BY 77 -> BZ 78 -> CA 79 -> CB
80 -> CC 81 -> CD 82 -> CE 83 -> CF 84 -> CG
85 -> CH 86 -> CI 87 -> CJ 88 -> CK 89 -> CL
90 -> CM 91 -> CN 92 -> CO 93 -> CP 94 -> CQ
PHP Generators
$isEven = function ($value) {
return !($value & 1);
};
$isOdd = function ($value) {
return $value & 1;
};
function xFilter(callable $callback, array $args=array()) {
foreach ($args as $arg)
if (call_user_func($callback, $arg))
yield $arg;
}
PHP Generators
$data = range(1,10);
echo 'xFilter for Odd Numbers', PHP_EOL;
foreach (xFilter($isOdd, $data) as $i)
echo('num is: '.$i.PHP_EOL);
echo 'xFilter for Even Numbers', PHP_EOL;
foreach (xFilter($isEven, $data) as $i)
echo('num is: '.$i.PHP_EOL);
xFilter for Odd Numbers
num is: 1
num is: 3
num is: 5
num is: 7
num is: 9
xFilter for Even Numbers
num is: 2
num is: 4
num is: 6
num is: 8
num is: 10
PHP Generators
• Can return both a value and a “pseudo” key• By default• The key is an integer value• Starting with 0 for the first iteration• Incrementing by 1 each iteration
• Accessed from foreach() as:foreach(generator() as $key => $value) {}
• or using$key = $generatorObject->key();
PHP Generators
• Default key behaviour can be changed• Syntax is:
yield $key => $value;
• Unlike array keys:• “Pseudo” keys can be any PHP datatype• “Pseudo” key values can be duplicated
PHP Generators
function xrange($lower, $upper) { $k = $upper; for ($i = $lower; $i <= $upper; ++$i) { yield $k-- => $i; }}
foreach (xrange(0, 8) as $i => $value) { echo $i, ' -> ', $value, PHP_EOL;}
8 -> 0
7 -> 1
6 -> 2
5 -> 3
4 -> 4
3 -> 5
2 -> 6
1 -> 7
0 -> 8
PHP Generators
function duplicateKeys($lower, $upper) {
for ($i = $lower; $i <= $upper; ++$i) {
yield (($i-1) % 3) + 1 => $i;
}
}
foreach (duplicateKeys(1,15) as $i => $value){
echo $i , ' -> ' , $value, PHP_EOL;
}
1 -> 1
2 -> 2
3 -> 3
1 -> 4
2 -> 5
3 -> 6
1 -> 7
2 -> 8
3 -> 9
1 -> 10
2 -> 11
3 -> 12
1 -> 13
2 -> 14
3 -> 15
PHP Generators
function duplicateKeys($string) {
$string = strtolower($string);
$length = strlen($string);
for ($i = 0; $i < $length; ++$i) {
yield strtoupper($string[$i]) => $string[$i];
}
}
foreach (duplicateKeys('badass') as $key => $value) {
echo $key , ' -> ' , $value, PHP_EOL;
}
B -> b
A -> a
D -> d
A -> a
S -> s
S -> s
PHP Generators
function floatKeys($lower, $upper) {
for ($i = $lower; $i <= $upper; ++$i) {
yield ($i / 5) => $i;
}
}
foreach (floatKeys(1,16) as $i => $value) {
printf(
'%0.2f -> %2d' . PHP_EOL,
$i,
$value
);
}
0.20 -> 1
0.40 -> 2
0.60 -> 3
0.80 -> 4
1.00 -> 5
1.20 -> 6
1.40 -> 7
1.60 -> 8
1.80 -> 9
2.00 -> 10
2.20 -> 11
2.40 -> 12
2.60 -> 13
2.80 -> 14
3.00 -> 15
3.20 -> 16
PHP Generators
• It is possible to access generated values “by reference”
• The generator must be declared “by reference”
• The yielded value must be a variable, and cannot be an expression
PHP Generators
function &byReference2($size) { for($val=1, $key=1; $key <= $size; ++$val, ++$key) { yield $key => $val; }}
$size = 10;foreach (byReference2($size) as $key => &$value) { echo $key, ' => ', $value, ' => ', ($value += $value - 1), PHP_EOL;}echo PHP_EOL;
1 => 1 => 1
2 => 2 => 3
3 => 4 => 7
4 => 8 => 15
5 => 16 => 31
6 => 32 => 63
7 => 64 => 127
8 => 128 => 255
9 => 256 => 511
10 => 512 => 1023
PHP Generators
• Data can be passed to the generator• Sometimes called a “Coroutine” when used in this way• Not strictly accurate, they are more strictly a “Semicoroutine”• They can form the basis for a “Coroutine” with the addition of a top-level
dispatcher routine
• Syntax is:$value = yield;
• Calling script uses the “send()” method:$generatorObject->send($value);
PHP Generators
$data = array(
'London',
'New York',
'Paris',
'Munich',
);
function generatorSend() {
while (true) {
$cityName = yield;
echo $cityName, PHP_EOL;
}
}
$generatorObject = generatorSend();
foreach($data as $value) {
$generatorObject->send($value);
}
LondonNew YorkParisMunich
PHP Generators$logFileName = __DIR__ . '/error.log';
function logger($logFileName) {
$f = fopen($logFileName, 'a');
while ($logentry = yield) {
fwrite(
$f,
(new DateTime())->format('Y-m-d H:i:s ') .
$logentry .
PHP_EOL
);
}
}
$logger = logger($logFileName);
for($i = 0; $i < 12; ++$i) {
$logger->send('Message #' . $i );
}
PHP Generators
• It is possible to combine a Generator to both send and accept data
PHP Generators
function generatorSend($limit) {
for ($i = 1; $i <= $limit; ++$i) {
yield $i => pow($i, $i);
$continue = yield;
if (!$continue)
break;
}
}
$generatorObject = generatorSend(100);
$carryOnRegardless = true;
while ($generatorObject->valid()) {
$key = $generatorObject->key();
$value = $generatorObject->current();
if ($key >= 10)
$carryOnRegardless = false;
$generatorObject->next();
$generatorObject->send($carryOnRegardless);
echo $key, ' -> ', $value, PHP_EOL;
}
1 -> 1
2 -> 4
3 -> 27
4 -> 256
5 -> 3125
6 -> 46656
7 -> 823543
8 -> 16777216
9 -> 387420489
10 -> 10000000000
PHP Generators (Gotcha)
function generatorSend($limit) {
for ($i = 1; $i <= $limit; ++$i) {
yield pow($i, $i);
$continue = yield;
if (!$continue)
break;
}
}
$generatorObject = generatorSend(100);
$carryOnRegardless = true;
while ($generatorObject->valid()) {
$key = $generatorObject->key();
$value = $generatorObject->current();
if ($key >= 10)
$carryOnRegardless = false;
$generatorObject->next();
$generatorObject->send($carryOnRegardless);
echo $key, ' -> ', $value, PHP_EOL;
}
0 -> 1
2 -> 4
4 -> 27
6 -> 256
8 -> 3125
10 -> 46656
PHP Generators
function generatorSend($limit) {
for ($i = 1; $i <= $limit; ++$i) {
$continue = (yield pow($i, $i));
if (!$continue)
break;
}
}
$generatorObject = generatorSend(100);
$carryOnRegardless = true;
while($generatorObject->valid()) {
$key = $generatorObject->key();
$value = $generatorObject->current();
echo $key, ' -> ', $value, PHP_EOL;
if ($key >= 10)
$carryOnRegardless = false;
$generatorObject->send($carryOnRegardless);
}
0 -> 1
1 -> 4
2 -> 27
3 -> 256
4 -> 3125
5 -> 46656
6 -> 823543
7 -> 16777216
8 -> 387420489
9 -> 10000000000
PHP Generators
• By sending data into a Generator it is possible to change its behaviour
PHP Generators
function diamond($size) { $i = $key = 1; do { $ascending = (yield $key => str_repeat(' ', $size - $i) . str_repeat('*', $i*2-1) . str_repeat(' ', $size - $i)); if ($ascending !== null) { ($ascending) ? ++$i : --$i; ++$key; } } while($i > 0);}
$size = 5;$diamond = diamond($size);foreach ($diamond as $key => $value) { echo sprintf('%2d', $key), ' => ', $value, PHP_EOL; $diamond->send($key < $size);}
1 => *
2 => ***
3 => *****
4 => *******
5 => *********
6 => *******
7 => *****
8 => ***
9 => *
PHP Generators
function adjustableIncrementor($value = 1, $increment = 1) { do { $increment = (yield $value); $value += $increment; } while ($value <= PHP_INT_MAX);}
$incrementor = adjustableIncrementor();foreach ($incrementor as $increment) { echo number_format($increment), PHP_EOL; $incrementor->send($increment);}
1
2
4
8
16
32
64
128
256
512
1,024
...
268,435,456
536,870,912
1,073,741,824
PHP Generators
function adjustableIncrementor($value = 1, $increment = 1) { do { $increment = (yield $value); $value += $increment; } while ($value <= PHP_INT_MAX);}
$incrementor = adjustableIncrementor();foreach ($incrementor as $increment) { echo number_format($increment), PHP_EOL; $incrementor->send(pow(10, strlen($increment)-1));}
1
2
3
...
9
10
20
30
...
90
100
200
300
...
900
1,000
...
PHP Generators
• We can also throw an Exception into a Generator
• A Try/Catch block should be defined in the Generator
• We use the Generator’s “throw()” method from the calling code$generatorObject->throw(new Exception(‘xyz’));
• Useful for terminating a Generator loop if we don’t want to code a send() in every iteration
PHP Generators
function filteredNumbers(Callable $filter) { $i = 1; try { do { if (call_user_func($filter, $i)) { yield $i; } } while ($i++ <= PHP_INT_MAX); } catch (Exception $e) { echo $e->getMessage(), PHP_EOL; }}
PHP Generators
$primes = filteredNumbers($isPrime);foreach ($primes as $counter => $prime) { if ($prime > 50) { $primes->throw( new Exception('Enough already') ); continue; } echo $prime, PHP_EOL;}
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
Enough already
PHP Generators
• You can’t pass Generators as arguments to array functions
So you can’t call array_map() or array_reduce()with a Generator instead of an array argument
• But by passing a Generator as an argument to anotherGenerator, we can chain Generators
Allowing us to simulate array_map() or array_reduce()
PHP Generators
function filteredNumbers(Callable $filter) { $i = 1; do { if (call_user_func($filter, $i)) { yield $i; } } while ($i++ <= PHP_INT_MAX);}
PHP Generators
function filteredValueLimit(Traversable $filter, $limit) { foreach ($filter as $value) { if ($value > $limit) { break; } yield $value; }}
PHP Generators
function mappedFilterList(Traversable $filter, Callable $callback) { foreach ($filter as $value) { yield $value => call_user_func($callback, $value); }}
PHP Generators
$primes = filteredNumbers($isPrime); $primes64 = filteredValueLimit($primes, 64); $primesSquared = mappedFilterList( $primes64, function($value) { return $value * $value; } );
foreach ($primesSquared as $primeSquared) { echo $prime, ' => ', $primeSquared, PHP_EOL;
}
2 => 4
3 => 9
5 => 25
7 => 49
11 => 121
13 => 169
17 => 289
19 => 361
23 => 529
29 => 841
31 => 961
37 => 1369
41 => 1681
43 => 1849
47 => 2209
53 => 2809
59 => 3481
61 => 3721
PHP Generators
function reduceFilterList(Traversable $filter, Callable $callback, $initial) { $result = $initial; foreach($filter as $value) { $result = call_user_func($callback, $value, $result); } yield $result;}
PHP Generators
$primes = filteredNumbers2($isPrime);$primes64 = filteredValueLimit($primes, 64);$sumPrimes = reduceFilterList( $primes64, function($value, $initial) { return $value + $initial; }, 0);
$sumPrime = $sumPrimes->current();echo $sumPrime, PHP_EOL;
501
PHP Generators
• Additional Reading:
• http://blog.ircmaxell.com/2012/07/what-generators-can-do-for-you.html• http://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html
Electrify your code with PHP Generators
?Questions
Who am I?
Mark BakerDesign and Development ManagerInnovEd (Innovative Solutions for Education) Learning Ltd
Coordinator and Developer of:Open Source PHPOffice library
PHPExcel, PHPWord, PHPPowerPoint, PHPProject, PHPVisioMinor contributor to PHP coreOther small open source libraries available on github
@Mark_Baker
https://github.com/MarkBaker
http://uk.linkedin.com/pub/mark-baker/b/572/171