+ All Categories
Home > Software > Electrify your code with PHP Generators

Electrify your code with PHP Generators

Date post: 26-Jul-2015
Category:
Upload: mark-baker
View: 337 times
Download: 1 times
Share this document with a friend
44
your Code with PHP Generators
Transcript
Page 1: Electrify your code with PHP Generators

Electrify your Code with

PHP Generators

Page 2: 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.

Page 3: Electrify your code with PHP Generators

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#

Page 4: Electrify your code with PHP Generators

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

Page 5: Electrify your code with PHP Generators

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) { }

Page 6: Electrify your code with PHP Generators

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

Page 7: Electrify your code with PHP Generators

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(); }

Page 8: Electrify your code with PHP Generators

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; }

Page 9: Electrify your code with PHP Generators

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

Page 10: Electrify your code with PHP Generators

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

Page 11: Electrify your code with PHP Generators

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;

}

Page 12: Electrify your code with PHP Generators

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

Page 13: Electrify your code with PHP Generators

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();

Page 14: Electrify your code with PHP Generators

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

Page 15: Electrify your code with PHP Generators

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

Page 16: Electrify your code with PHP Generators

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

Page 17: Electrify your code with PHP Generators

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

Page 18: Electrify your code with PHP Generators

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

Page 19: Electrify your code with PHP Generators

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

Page 20: Electrify your code with PHP Generators

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

Page 21: Electrify your code with PHP Generators

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);

Page 22: Electrify your code with PHP Generators

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

Page 23: Electrify your code with PHP Generators

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 );

}

Page 24: Electrify your code with PHP Generators

PHP Generators

• It is possible to combine a Generator to both send and accept data

Page 25: Electrify your code with PHP Generators

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

Page 26: Electrify your code with PHP Generators

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

Page 27: Electrify your code with PHP Generators

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

Page 28: Electrify your code with PHP Generators

PHP Generators

• By sending data into a Generator it is possible to change its behaviour

Page 29: Electrify your code with PHP Generators

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 => *

Page 30: Electrify your code with PHP Generators

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

Page 31: Electrify your code with PHP Generators

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

...

Page 32: Electrify your code with PHP Generators

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

Page 33: Electrify your code with PHP Generators

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; }}

Page 34: Electrify your code with PHP Generators

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

Page 35: Electrify your code with PHP Generators

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()

Page 36: Electrify your code with PHP Generators

PHP Generators

function filteredNumbers(Callable $filter) { $i = 1; do { if (call_user_func($filter, $i)) { yield $i; } } while ($i++ <= PHP_INT_MAX);}

Page 37: Electrify your code with PHP Generators

PHP Generators

function filteredValueLimit(Traversable $filter, $limit) { foreach ($filter as $value) { if ($value > $limit) { break; } yield $value; }}

Page 38: Electrify your code with PHP Generators

PHP Generators

function mappedFilterList(Traversable $filter, Callable $callback) { foreach ($filter as $value) { yield $value => call_user_func($callback, $value); }}

Page 39: Electrify your code with PHP Generators

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

Page 40: Electrify your code with PHP Generators

PHP Generators

function reduceFilterList(Traversable $filter, Callable $callback, $initial) { $result = $initial; foreach($filter as $value) { $result = call_user_func($callback, $value, $result); } yield $result;}

Page 41: Electrify your code with PHP Generators

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

Page 42: Electrify your code with PHP Generators

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

Page 43: Electrify your code with PHP Generators

Electrify your code with PHP Generators

?Questions

Page 44: Electrify your code with PHP Generators

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


Recommended