Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Electrify your code with PHP Generators

2,353 views

Published on

Slides from presentation on PHP 5.5 Generators given to various user groups around the UK during 2015/16

Published in: Software
  • Be the first to comment

Electrify your code with PHP Generators

  1. 1. Electrify your Code with PHP Generators
  2. 2. PHP Generators • http://www.slideshare.net/MarkBakerUK/generators-49071693
  3. 3. 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.
  4. 4. 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#
  5. 5. 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
  6. 6. PHP Generators • Automatically created when PHP identifies a function or method containing the “yield” keyword function myGeneration() { yield 1; } $generator = myGeneration(); var_dump($generator); object(Generator)#1 (0) { }
  7. 7. 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
  8. 8. 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(); }
  9. 9. 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; }
  10. 10. 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
  11. 11. 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
  12. 12. 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; }
  13. 13. 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
  14. 14. 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();
  15. 15. 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
  16. 16. 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
  17. 17. 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
  18. 18. 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
  19. 19. 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
  20. 20. 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
  21. 21. 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
  22. 22. 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);
  23. 23. PHP Generators $data = array( 'Squirtle', 'Jigglypuff', 'Charmander', 'Bulbasaur', 'White DPC Elephpant', ); function generatorSend() { while (true) { $cityName = yield; echo $cityName, PHP_EOL; } } $generatorObject = generatorSend(); foreach($data as $value) { $generatorObject->send($value); } echo PHP_EOL, 'Gotta Collect 'em all', PHP_EOL; Squirtle Jigglypuff Charmander Bulbasaur White DPC Elephpant Gotta Collect 'em All
  24. 24. 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 ); }
  25. 25. PHP Generators • It is possible to combine a Generator to both send and accept data
  26. 26. PHP Generators function generatorSend($limit) { for ($i = 1; $i <= $limit; ++$i) { yield $i => pow($i, $i); $continue = yield; if (!$continue) break; } } $generatorObject = generatorSend(100); while ($generatorObject->valid()) { $key = $generatorObject->key(); $value = $generatorObject->current(); $generatorObject->next(); $generatorObject->send($key >= 10); 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
  27. 27. PHP Generators (Gotcha) function generatorSend($limit) { for ($i = 1; $i <= $limit; ++$i) { yield pow($i, $i); $continue = yield; if (!$continue) break; } } $generatorObject = generatorSend(100); while ($generatorObject->valid()) { $key = $generatorObject->key(); $value = $generatorObject->current(); $generatorObject->next(); $generatorObject->send($key >= 10); echo $key, ' -> ', $value, PHP_EOL; } 0 -> 1 2 -> 4 4 -> 27 6 -> 256 8 -> 3125 10 -> 46656
  28. 28. PHP Generators function generatorSend($limit) { for ($i = 1; $i <= $limit; ++$i) { $continue = (yield pow($i, $i)); if (!$continue) break; } } $generatorObject = generatorSend(100); while($generatorObject->valid()) { $key = $generatorObject->key(); $value = $generatorObject->current(); echo $key, ' -> ', $value, PHP_EOL; $generatorObject->send($key >= 10); } 0 -> 1 1 -> 4 2 -> 27 3 -> 256 4 -> 3125 5 -> 46656 6 -> 823543 7 -> 16777216 8 -> 387420489 9 -> 10000000000
  29. 29. PHP Generators (Gotcha) function generatorSend($limit) { for ($i = 1; $i <= $limit; ++$i) { $continue = (yield pow($i, $i)); if (!$continue) break; } } $generatorObject = generatorSend(100); foreach($generatorObject as $key => $value) { echo $key, ' -> ', $value, PHP_EOL; $generatorObject->send($key >= 10); } 0 -> 1
  30. 30. PHP Generators (Gotcha) function generatorSend($limit) { for ($i = 1; $i <= $limit; ++$i) { $continue = (yield pow($i, $i)); if (!$continue && $continue !== null) break; } } $generatorObject = generatorSend(100); foreach($generatorObject as $key => $value) { echo $key, ' -> ', $value, PHP_EOL; $generatorObject->send($key >= 10); } 1 -> 1 3 -> 27 5 -> 3125 7 -> 823543 9 -> 387420489 11 -> 285311670611
  31. 31. PHP Generators (Gotcha) function generatorSend($limit) { for ($i = 1; $i <= $limit; ++$i) { $continue = (yield pow($i, $i)); if (!$continue && $continue !== null) break; elseif (!$continue && $continue === null) --$i; } } $generatorObject = generatorSend(100); foreach($generatorObject as $key => $value) { echo $key, ' -> ', $value, PHP_EOL; $generatorObject->send($key >= 10); } 1 -> 1 2 -> 4 3 -> 27 4 -> 256 5 -> 3125 6 -> 46656 7 -> 823543 8 -> 16777216 9 -> 387420489 10 -> 10000000000
  32. 32. PHP Generators • By sending data into a Generator it is possible to change its behaviour
  33. 33. 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 => *
  34. 34. 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
  35. 35. 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 ...
  36. 36. 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
  37. 37. 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; } }
  38. 38. 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
  39. 39. 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 another Generator, we can chain Generators Allowing us to simulate array_map() or array_reduce()
  40. 40. PHP Generators function filteredNumbers(Callable $filter) { $i = 1; do { if (call_user_func($filter, $i)) { yield $i; } } while ($i++ <= PHP_INT_MAX); }
  41. 41. PHP Generators function filteredValueLimit(Traversable $filter, $limit) { foreach ($filter as $value) { if ($value > $limit) { break; } yield $value; } }
  42. 42. PHP Generators function mappedFilterList(Traversable $filter, Callable $callback) { foreach ($filter as $value) { yield $value => call_user_func($callback, $value); } }
  43. 43. 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
  44. 44. PHP Generators function reduceFilterList(Traversable $filter, Callable $callback, $initial) { $result = $initial; foreach($filter as $value) { $result = call_user_func($callback, $value, $result); } yield $result; }
  45. 45. 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
  46. 46. PHP Generators • Limitations • Can’t be Extended • Can’t be Serialized • Can’t be “Rewound”
  47. 47. 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 • https://markbakeruk.net/2016/01/19/a-functional-guide-to-cat-herding-with-php- generators/
  48. 48. Electrify your code with PHP Generators ? Questions
  49. 49. Who am I? Mark Baker Design and Development Manager InnovEd (Innovative Solutions for Education) Learning Ltd Coordinator and Developer of: Open Source PHPOffice library PHPExcel, PHPWord, PHPPowerPoint, PHPProject, PHPVisio Minor contributor to PHP core Other small open source libraries available on github @Mark_Baker https://github.com/MarkBaker http://uk.linkedin.com/pub/mark-baker/b/572/171 http://markbakeruk.net

×