Marvelous Agents of Yield

Marvelous Agents of Yield

Generators and thus yield are part of PHP since version 5.5. Most of the time it is used as a substitution for a full iterator implementation.
But there is way more power to generators! This talk will try to unveil the hidden superpowers of interruptible loops, signals and corporative multitasking. Coincidental movie analogies included.

Talk given at the PHP USERGROUP DRESDEN e.V. meetup.

8ad631306f5ab343446a967b98e64c0e?s=128

Holger Woltersdorf

August 22, 2018
Tweet

Transcript

  1. 2.
  2. 6.

    FITZ'PLANATION The heart of a generator function is the yield

    keyword. 
 In its simplest form, a yield statement looks much like a return statement, except that instead of stopping execution of the function and returning, yield instead provides a value to the code looping over the generator and pauses execution of the generator function. php.net @hollodotme
  3. 8.

    JEMMA'LOC function generate() { yield 1; } $generator = generate();

    echo 'Type: ', gettype($generator), PHP_EOL; echo 'Class: ', get_class($generator), PHP_EOL; echo 'Is ', (is_callable($generator) ? 'callable' : 'not callable'), PHP_EOL; $generatorClone = clone $generator; @hollodotme
  4. 9.

    JEMMA'LOC function generate() { yield 1; } $generator = generate();

    echo 'Type: ', gettype($generator), PHP_EOL; echo 'Class: ', get_class($generator), PHP_EOL; echo 'Is ', (is_callable($generator) ? 'callable' : 'not callable'), PHP_EOL; $generatorClone = clone $generator; — Type: object Class: Generator Is not callable Fatal error: Uncaught Error: Trying to clone an uncloneable object of class Generator @hollodotme
  5. 10.

    JEMMA'LOC function range( int $start, int $limit ) : array

    { $elements = []; for ( $i = $start; $i !<= $limit; $i!++ ) { $elements[] = $i; } return $elements; } # vs. function xrange( int $start, int $limit ) : Generator { for ( $i = $start; $i !<= $limit; $i!++ ) { yield $i; } } @hollodotme
  6. 11.

    JEMMA'LOC foreach ( range( 1, 10000 ) as $i )

    { print $i . '.'; } # 2 MiB foreach ( range( 1, 100000 ) as $i ) { print $i . '.'; } # 6 MiB foreach ( range( 1, 1000000 ) as $i ) { print $i . '.'; } # 34 MiB @hollodotme
  7. 12.

    JEMMA'LOC foreach ( xrange( 1, 10000 ) as $i )

    { print $i . '.'; } # 2 MiB foreach ( xrange( 1, 100000 ) as $i ) { print $i . '.'; } # 2 MiB foreach ( xrange( 1, 1000000 ) as $i ) { print $i . '.'; } # 2 MiB @hollodotme
  8. 13.

    JEMMA'LOC # OPCODES OF RANGE() ~$ php -d vld.active=1 -d

    vld.execute=0 -f range.func.php number of ops: 22 compiled vars: !0 = $start, !1 = $limit, !2 = $step, !3 = $elements, !4 = $i # OPCODES OF XRANGE() ~$ php -d vld.active=1 -d vld.execute=0 -f xrange.func.php number of ops: 16 compiled vars: !0 = $start, !1 = $limit, !2 = $step, !3 = $i @hollodotme
  9. 14.

    JEMMA'LOC function generate() { print 'generate - start' . PHP_EOL;

    for ( $i = 1; $i !<= 5; $i!++ ) { print 'generate - yielding!!...' . PHP_EOL; yield $i; print 'generate - continued' . PHP_EOL; } print 'generate - end' . PHP_EOL; } $generator = generate(); print 'Generator created' . PHP_EOL; while ( $generator!->valid() ) { print 'Loop gets current value' . PHP_EOL; print $generator!->current() . PHP_EOL; $generator!->next(); } @hollodotme
  10. 15.

    JEMMA'LOC function generate() { print 'generate - start' . PHP_EOL;

    for ( $i = 1; $i !<= 5; $i!++ ) { print 'generate - yielding!!...' . PHP_EOL; yield $i; print 'generate - continued' . PHP_EOL; } print 'generate - end' . PHP_EOL; } $generator = generate(); print 'Generator created' . PHP_EOL; while ( $generator!->valid() ) { print 'Loop gets current value' . PHP_EOL; print $generator!->current() . PHP_EOL; $generator!->next(); } Generator created generate - start generate - yielding!!... Loop gets current value 1 generate - continued generate - yielding!!... Loop gets current value 2 generate - continued generate - yielding!!... Loop gets current value 3 generate - continued generate - yielding!!... Loop gets current value 4 generate - continued generate - yielding!!... Loop gets current value 5 generate - continued generate - end @hollodotme
  11. 16.

    JEMMA'LOC function generate() { print 'generate - start' . PHP_EOL;

    for ( $i = 1; $i !<= 5; $i!++ ) { print 'generate - yielding!!...' . PHP_EOL; yield $i; print 'generate - continued' . PHP_EOL; } print 'generate - end' . PHP_EOL; } $generator = generate(); print 'Generator created' . PHP_EOL; while ( $generator!->valid() ) { print 'Loop gets current value' . PHP_EOL; print $generator!->current() . PHP_EOL; $generator!->next(); } Generator created generate - start generate - yielding!!... Loop gets current value 1 generate - continued generate - yielding!!... Loop gets current value 2 generate - continued generate - yielding!!... Loop gets current value 3 generate - continued generate - yielding!!... Loop gets current value 4 generate - continued generate - yielding!!... Loop gets current value 5 generate - continued generate - end @hollodotme
  12. 17.

    JEMMA'LOC function generate() { print 'generate - start' . PHP_EOL;

    for ( $i = 1; $i !<= 5; $i!++ ) { print 'generate - yielding!!...' . PHP_EOL; yield $i; print 'generate - continued' . PHP_EOL; } print 'generate - end' . PHP_EOL; } $generator = generate(); print 'Generator created' . PHP_EOL; while ( $generator!->valid() ) { print 'Loop gets current value' . PHP_EOL; print $generator!->current() . PHP_EOL; $generator!->next(); } Generator created generate - start generate - yielding!!... Loop gets current value 1 generate - continued generate - yielding!!... Loop gets current value 2 generate - continued generate - yielding!!... Loop gets current value 3 generate - continued generate - yielding!!... Loop gets current value 4 generate - continued generate - yielding!!... Loop gets current value 5 generate - continued generate - end @hollodotme
  13. 18.

    JEMMA'LOC function generate() { print 'generate - start' . PHP_EOL;

    for ( $i = 1; $i !<= 5; $i!++ ) { print 'generate - yielding!!...' . PHP_EOL; yield $i; print 'generate - continued' . PHP_EOL; } print 'generate - end' . PHP_EOL; } $generator = generate(); print 'Generator created' . PHP_EOL; while ( $generator!->valid() ) { print 'Loop gets current value' . PHP_EOL; print $generator!->current() . PHP_EOL; $generator!->next(); } Generator created generate - start generate - yielding!!... Loop gets current value 1 generate - continued generate - yielding!!... Loop gets current value 2 generate - continued generate - yielding!!... Loop gets current value 3 generate - continued generate - yielding!!... Loop gets current value 4 generate - continued generate - yielding!!... Loop gets current value 5 generate - continued generate - end @hollodotme
  14. 19.

    JEMMA'LOC function generate() { print 'generate - start' . PHP_EOL;

    for ( $i = 1; $i !<= 5; $i!++ ) { print 'generate - yielding!!...' . PHP_EOL; yield $i; print 'generate - continued' . PHP_EOL; } print 'generate - end' . PHP_EOL; } $generator = generate(); print 'Generator created' . PHP_EOL; while ( $generator!->valid() ) { print 'Loop gets current value' . PHP_EOL; print $generator!->current() . PHP_EOL; $generator!->next(); } Generator created generate - start generate - yielding!!... Loop gets current value 1 generate - continued generate - yielding!!... Loop gets current value 2 generate - continued generate - yielding!!... Loop gets current value 3 generate - continued generate - yielding!!... Loop gets current value 4 generate - continued generate - yielding!!... Loop gets current value 5 generate - continued generate - end @hollodotme
  15. 23.

    FITZ'PLANATION The primary advantage of generators is their simplicity. Much

    less boilerplate code has to be written compared to implementing an Iterator class, and the code is generally much more readable. php.net @hollodotme
  16. 24.

    JEMMA'LOC $generator = xrange( 1, 100 ); while ( $generator!->valid()

    ) { echo $generator!->current() . '.'; $generator!->next(); } # The Iterator interface interface Iterator extends Traversable { public function current(); public function next(); public function key(); public function valid(); public function rewind(); } @hollodotme
  17. 28.

    JEMMA'LOC $generator = xrange( 1, 100 ); count($generator); — PHP

    Warning: count(): Parameter must be an array or an object that implements Countable @hollodotme
  18. 29.

    FITZ'PLANATION A generator is a highly dynamic construct with an

    iterator API, but an unknown number of elements. It’s not a data structure like an array or a common object. hw @hollodotme
  19. 30.

    JEMMA'LOC $generator = xrange( 1, 100 ); $count = count(

    iterator_to_array( $generator ) ); echo 'Count: ', $count, PHP_EOL; $count = 0; foreach ( $generator as $item ) { $count!++; } echo 'Count: ', $count, PHP_EOL; @hollodotme
  20. 31.

    JEMMA'LOC $generator = xrange( 1, 100 ); $count = count(

    iterator_to_array( $generator ) ); echo 'Count: ', $count, PHP_EOL; $count = 0; foreach ( $generator as $item ) { $count!++; } echo 'Count: ', $count, PHP_EOL; — Count: 100 Exception: Cannot traverse an already closed generator @hollodotme
  21. 32.

    JEMMA'LOC $generator = xrange( 1, 100 ); $count = count(

    iterator_to_array( $generator ) ); echo 'Count: ', $count, PHP_EOL; $generator!->rewind(); $count = 0; foreach ( $generator as $item ) { $count!++; } echo 'Count: ', $count, PHP_EOL; @hollodotme
  22. 33.

    JEMMA'LOC $generator = xrange( 1, 100 ); $count = count(

    iterator_to_array( $generator ) ); echo 'Count: ', $count, PHP_EOL; $generator!->rewind(); $count = 0; foreach ( $generator as $item ) { $count!++; } echo 'Count: ', $count, PHP_EOL; — Count: 100 Exception: Cannot rewind a generator that was already run @hollodotme
  23. 35.

    FITZ'PLANATION […] generators are forward-only iterators, and cannot be rewound

    once iteration has started. This also means that the same generator can't be iterated over multiple times: the generator will need to be rebuilt by calling the generator function again. php.net @hollodotme
  24. 38.

    JEMMA'LOC $generator = xrange( 1, 100 ); $count = count(

    iterator_to_array( $generator ) ); echo 'Count: ', $count, PHP_EOL; # Create new generator $generator = xrange( 1, 100 ); $count = 0; foreach ( $generator as $item ) { $count!++; } echo 'Count: ', $count, PHP_EOL; — Count: 100 Count: 100 @hollodotme
  25. 40.

    JEMMA'LOC function countToTen() : Generator { yield 1; for (

    $i = 2; $i < 4; $i!++ ) { yield $i; } yield from [4, 5]; yield from json_decode( '[6,7]' ); yield from eightNine(); yield 10; } function eightNine() : Generator { yield '8 ' !=> 9; } foreach ( countToTen() as $key !=> $number ) print (is_string($key) ? $key : '') . $number . ' '; @hollodotme
  26. 41.

    JEMMA'LOC function countToTen() : Generator { yield 1; for (

    $i = 2; $i < 4; $i!++ ) { yield $i; } yield from [4, 5]; yield from json_decode( '[6,7]' ); yield from eightNine(); yield 10; } function eightNine() : Generator { yield '8 ' !=> 9; } foreach ( countToTen() as $key !=> $number ) print (is_string($key) ? $key : '') . $number . ' '; — 1 2 3 4 5 6 7 8 9 10 @hollodotme
  27. 43.

    JEMMA'LOC function gen() { $key = new stdClass(); $key!->property =

    'I am an object!'; yield $key !=> '& I am a simple string!'; } $generator = gen(); echo $generator!->key()!->property, ' ', $generator!->current(); @hollodotme
  28. 44.

    JEMMA'LOC function gen() { $key = new stdClass(); $key!->property =

    'I am an object!'; yield $key !=> '& I am a simple string!'; } $generator = gen(); echo $generator!->key()!->property, ' ', $generator!->current(); — I am an object! & I am a simple string! @hollodotme
  29. 45.

    JEMMA'LOC $key = new stdClass(); $key!->property = 'I am an

    object!'; $array = [$key !=> '& I am a simple string.']; echo key($array)!->property, ' ', current($array); @hollodotme
  30. 46.

    JEMMA'LOC $key = new stdClass(); $key!->property = 'I am an

    object!'; $array = [$key !=> '& I am a simple string.']; echo key($array)!->property, ' ', current($array); — PHP Warning: Illegal offset type in line 4 PHP Notice: Trying to get property 'property' of non-object in line 6 NO OUTPUT!!! @hollodotme
  31. 48.

    JEMMA'LOC function genFibonacci( int $loops ) : Generator { $fibonacci

    = ''; for ( $i = 0; $i < $loops; $i!++ ) { yield $i; $fibonacci !.= round( pow( (sqrt( 5 ) + 1) / 2, $i ) / sqrt( 5 ) ); } return $fibonacci; } $generator = genFibonacci( 10 ); while ( $generator!->valid() ) { print $generator!->current(); $generator!->next(); } print PHP_EOL . 'Fibonacci: ' . $generator!->getReturn() . PHP_EOL; @hollodotme
  32. 49.

    JEMMA'LOC function genFibonacci( int $loops ) : Generator { $fibonacci

    = ''; for ( $i = 0; $i < $loops; $i!++ ) { yield $i; $fibonacci !.= round( pow( (sqrt( 5 ) + 1) / 2, $i ) / sqrt( 5 ) ); } return $fibonacci; } $generator = genFibonacci( 10 ); while ( $generator!->valid() ) { print $generator!->current(); $generator!->next(); } print PHP_EOL . 'Fibonacci: ' . $generator!->getReturn() . PHP_EOL; @hollodotme
  33. 50.

    JEMMA'LOC function genFibonacci( int $loops ) : Generator { $fibonacci

    = ''; for ( $i = 0; $i < $loops; $i!++ ) { yield $i; $fibonacci !.= round( pow( (sqrt( 5 ) + 1) / 2, $i ) / sqrt( 5 ) ); } return $fibonacci; } $generator = genFibonacci( 10 ); while ( $generator!->valid() ) { print $generator!->current(); $generator!->next(); } print PHP_EOL . 'Fibonacci: ' . $generator!->getReturn() . PHP_EOL; — 0123456789 Fibonacci: 0112358132134 @hollodotme
  34. 54.

    RECAP • A yield in a function or method always

    creates a Generator object • yield can provide a value to the caller of the Generator and pauses execution of its local context • Generator implements the Iterator interface, but can be consumed only once and forward-only • Generator can return a value when its execution stops @hollodotme
  35. 58.

    FITZ'PLANATION Coroutines and concurrency are largely orthogonal. Coroutines are a

    general control structure whereby flow control is cooperatively passed between two different routines without returning. stackoverflow.com @hollodotme
  36. 59.

    JEMMA'LOC function queue( PDO $pdo ) : Generator { $pdo!->query(

    'CREATE TABLE queue (message TEXT)' ); $statement = $pdo!->prepare( 'INSERT INTO queue (message) VALUES (:message)' ); while ( true ) { $message = yield; $statement!->execute( ['message' !=> $message] ); print 'Sent: ' . $message . PHP_EOL; } } $queue = queue( new PDO( 'sqlite!::memory:' ) ); $queue!->send( 'Foo' ); $queue!->send( 'Bar' ); @hollodotme
  37. 60.

    JEMMA'LOC function queue( PDO $pdo ) : Generator { $pdo!->query(

    'CREATE TABLE queue (message TEXT)' ); $statement = $pdo!->prepare( 'INSERT INTO queue (message) VALUES (:message)' ); while ( true ) { $message = yield; $statement!->execute( ['message' !=> $message] ); print 'Sent: ' . $message . PHP_EOL; } } $queue = queue( new PDO( 'sqlite!::memory:' ) ); $queue!->send( 'Foo' ); $queue!->send( 'Bar' ); @hollodotme
  38. 61.

    JEMMA'LOC function queue( PDO $pdo ) : Generator { $pdo!->query(

    'CREATE TABLE queue (message TEXT)' ); $statement = $pdo!->prepare( 'INSERT INTO queue (message) VALUES (:message)' ); while ( true ) { $message = yield; $statement!->execute( ['message' !=> $message] ); print 'Sent: ' . $message . PHP_EOL; } } $queue = queue( new PDO( 'sqlite!::memory:' ) ); $queue!->send( 'Foo' ); $queue!->send( 'Bar' ); — Sent: Foo Sent: Bar @hollodotme
  39. 62.

    JEMMA'LOC function queue( PDO $pdo ) : Generator { $pdo!->query(

    'CREATE TABLE queue (message TEXT)' ); $statement = $pdo!->prepare( 'INSERT INTO queue (message) VALUES (:message)' ); $counter = 0; while ( true ) { $message = yield $counter!++; $statement!->execute( ['message' !=> $message] ); print 'Sent: ' . $message; } } $queue = queue( new PDO( 'sqlite!::memory:' ) ); $queue!->send( 'Foo' ); print ' (' . $queue!->current() . ')' . PHP_EOL; $queue!->send( 'Bar' ); print ' (' . $queue!->current() . ')' . PHP_EOL; @hollodotme
  40. 63.

    JEMMA'LOC function queue( PDO $pdo ) : Generator { $pdo!->query(

    'CREATE TABLE queue (message TEXT)' ); $statement = $pdo!->prepare( 'INSERT INTO queue (message) VALUES (:message)' ); $counter = 0; while ( true ) { $message = yield $counter!++; $statement!->execute( ['message' !=> $message] ); print 'Sent: ' . $message; } } $queue = queue( new PDO( 'sqlite!::memory:' ) ); $queue!->send( 'Foo' ); print ' (' . $queue!->current() . ')' . PHP_EOL; $queue!->send( 'Bar' ); print ' (' . $queue!->current() . ')' . PHP_EOL; @hollodotme
  41. 67.

    JEMMA'LOC Sent: Foo (1) Sent: Bar (2) — $queue =

    queue( new PDO( 'sqlite!::memory:' ) ); print 'Hidden first value: ' . $queue!->current() . PHP_EOL; $queue!->send( 'Foo' ); print ' (' . $queue!->current() . ')' . PHP_EOL; $queue!->send( 'Bar' ); print ' (' . $queue!->current() . ')' . PHP_EOL; @hollodotme
  42. 68.

    JEMMA'LOC Sent: Foo (1) Sent: Bar (2) — $queue =

    queue( new PDO( 'sqlite!::memory:' ) ); print 'Hidden first value: ' . $queue!->current() . PHP_EOL; $queue!->send( 'Foo' ); print ' (' . $queue!->current() . ')' . PHP_EOL; $queue!->send( 'Bar' ); print ' (' . $queue!->current() . ')' . PHP_EOL; — Hidden first value: 0 Sent: Foo (1) Sent: Bar (2) @hollodotme
  43. 69.

    JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); print $gen!->send( '' ) . PHP_EOL; @hollodotme
  44. 70.

    JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); print $gen!->send( '' ) . PHP_EOL; — bar @hollodotme
  45. 71.

    JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); $gen!->rewind(); print $gen!->send( '' ) . PHP_EOL; @hollodotme
  46. 72.

    JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); $gen!->rewind(); print $gen!->send( '' ) . PHP_EOL; — bar @hollodotme
  47. 73.

    JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); $gen!->rewind(); # can be omitted print $gen!->current() . PHP_EOL; print $gen!->send( '' ) . PHP_EOL; @hollodotme
  48. 74.

    JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); $gen!->rewind(); # can be omitted print $gen!->current() . PHP_EOL; print $gen!->send( '' ) . PHP_EOL; — foo bar @hollodotme
  49. 75.

    JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); $gen!->rewind(); # can be omitted print $gen!->current() . PHP_EOL; print $gen!->send( '' ) . PHP_EOL; — foo bar COPY THAT. @hollodotme
  50. 76.

    JEMMA'LOC interface Traversable { # Marker interface, cannot be implemented

    directly } interface Iterator extends Traversable { public function rewind() : void; public function key(); public function current(); public function next() : void; public function valid() : bool; } interface Generator extends Iterator { public function getReturn(); public function send( $value ) : void; public function throw( Throwable $throwable ) : void; public function !__wakeup() : void; } @hollodotme
  51. 77.

    JEMMA'LOC interface Traversable { # Marker interface, cannot be implemented

    directly } interface Iterator extends Traversable { public function rewind() : void; public function key(); public function current(); public function next() : void; public function valid() : bool; } interface Generator extends Iterator { public function getReturn(); public function send( $value ) : void; public function throw( Throwable $throwable ) : void; public function !__wakeup() : void; } @hollodotme
  52. 78.

    JEMMA'LOC function gen() { yield 1; } $generator = gen();

    $generator!->!__wakeup(); @hollodotme
  53. 79.

    JEMMA'LOC function gen() { yield 1; } $generator = gen();

    $generator!->!__wakeup(); — PHP Fatal error: Uncaught Exception: Unserialization of 'Generator' is not allowed @hollodotme
  54. 80.

    JEMMA'LOC function gen() { yield 1; } $generator = gen();

    $generator!->!__wakeup(); — PHP Fatal error: Uncaught Exception: Unserialization of 'Generator' is not allowed @hollodotme DON’T!
  55. 81.

    JEMMA'LOC function gen() : Generator { while ( true )

    { try { echo "\n", yield; } catch ( LogicException $e ) { echo 'Exception: ' . $e!->getMessage(); } } } $generator = gen(); $generator!->send( 'Hello ' ); $generator!->throw( new LogicException( 'Raised inside the generator.' ) ); $generator!->send( 'World' ); $generator!->send( 'of tomorrow' ); @hollodotme
  56. 82.

    JEMMA'LOC function gen() : Generator { while ( true )

    { try { echo "\n", yield; } catch ( LogicException $e ) { echo 'Exception: ' . $e!->getMessage(); } } } $generator = gen(); $generator!->send( 'Hello ' ); $generator!->throw( new LogicException( 'Raised inside the generator.' ) ); $generator!->send( 'World' ); $generator!->send( 'of tomorrow' ); — Hello Exception: Raised inside the generator. World of tomorrow @hollodotme
  57. 83.

    JEMMA'LOC function gen() : Generator { while ( true )

    { try { echo "\n", yield; } catch ( RuntimeException $e ) { echo $e!->getMessage(); break; } } } $generator = gen(); $generator!->send( 'Hello ' ); $generator!->send( 'World' ); $generator!->throw( new RuntimeException( 'Stopping coroutine.' ) ); $generator!->send( 'of tomorrow' ); @hollodotme
  58. 84.

    JEMMA'LOC function gen() : Generator { while ( true )

    { try { echo "\n", yield; } catch ( RuntimeException $e ) { echo $e!->getMessage(); break; } } } $generator = gen(); $generator!->send( 'Hello ' ); $generator!->send( 'World' ); $generator!->throw( new RuntimeException( 'Stopping coroutine.' ) ); $generator!->send( 'of tomorrow' ); — Hello World Stopping coroutine. @hollodotme
  59. 85.

    RECAP • Through yield a value can be sent to

    the generator to continue its execution • Through yield an exception can be raised in a generator • yield provides first and receives second • You can start a generator by calling any of its Iterator methods • rewind ignores the first provided value @hollodotme
  60. 88.

    FITZ'PLANATION Co-operative multitasking, also k n o w n a

    s n o n - p r e e m p t i v e multitasking, is a style of computer multitasking in which the operating system never initiates a context switch from a running process to another process. Instead, processes voluntarily yield control periodically or when idle or logically blocked in order to enable multiple applications to be run concurrently. wikipedia.org @hollodotme
  61. 90.

    final class TheFramework { private $backup, $agents = []; public

    function !__construct() { $this!->backup = new SplQueue(); } public function newAgent( string $agentName, Generator $coroutine ) : void { $agent = new Agent( $agentName, $coroutine ); $this!->agents[ $agentName ] = $agent; $this!->sendToBattle( $agent ); } public function sendToBattle( Agent $task ) : void { $this!->backup!->enqueue( $task ); } public function fight() : void { while ( !$this!->backup!->isEmpty() ) { $agent = $this!->backup!->dequeue(); $retval = $agent!->fight(); if ( $retval instanceof SystemCall ) { $retval( $agent, $this ); continue; } if ( $agent!->outOfMunitions() ) { unset( $this!->agents[ $agent!->getName() ] ); continue; } $this!->sendToBattle( $agent ); } } } @hollodotme
  62. 91.

    final class Agent { private $name, $coroutine, $sendValue; private $beforeFirstYield

    = true; public function !__construct( string $name, Generator $coroutine ) { $this!->name = $name; $this!->coroutine = $coroutine; } public function getName() : string { return $this!->name; } public function setSendValue( $sendValue ) : void { $this!->sendValue = $sendValue; } public function fight() { if ( $this!->beforeFirstYield ) { $this!->beforeFirstYield = false; return $this!->coroutine!->current(); } $retval = $this!->coroutine!->send( $this!->sendValue ); $this!->sendValue = null; return $retval; } public function outOfMunitions() : bool { return !$this!->coroutine!->valid(); } } @hollodotme
  63. 92.

    final class SystemCall { private $callback; public function !__construct( callable

    $callback ) { $this!->callback = $callback; } public function !__invoke( Agent $agent, TheFramework $battleGround ) { $callback = $this!->callback; return $callback( $agent, $battleGround ); } } @hollodotme
  64. 93.

    # System call corouting function getAgentName() { return new SystemCall(

    function ( Agent $agent, TheFramework $battleGround ) { $agent!->setSendValue( $agent!->getName() ); $battleGround!->sendToBattle( $agent ); } ); } # Agent of yield function agent( $shots ) { $agentName = yield getAgentName(); for ( $i = 1; $i !<= $shots; !++$i ) { print "Agent {$agentName} fires shot {$i}." . PHP_EOL; yield; } print "Agent {$agentName} is out of munitions." . PHP_EOL; } @hollodotme
  65. 95.

    $battleGround = new TheFramework(); $battleGround!->newAgent( 'Coulson', agent( 12 ) );

    $battleGround!->newAgent( 'Mack', agent( 5 ) ); $battleGround!->fight(); print 'Game Over' . PHP_EOL; @hollodotme
  66. 96.

    $battleGround = new TheFramework(); $battleGround!->newAgent( 'Coulson', agent( 12 ) );

    $battleGround!->newAgent( 'Mack', agent( 5 ) ); $battleGround!->fight(); print 'Game Over' . PHP_EOL; @hollodotme Agent Coulson fires shot 1. Agent Mack fires shot 1. Agent Coulson fires shot 2. Agent Mack fires shot 2. Agent Coulson fires shot 3. Agent Mack fires shot 3. Agent Coulson fires shot 4. Agent Mack fires shot 4. Agent Coulson fires shot 5. Agent Mack fires shot 5. Agent Coulson fires shot 6. Agent Mack is out of munitions. Agent Coulson fires shot 7. Agent Coulson fires shot 8. Agent Coulson fires shot 9. Agent Coulson fires shot 10. Agent Coulson fires shot 11. Agent Coulson fires shot 12. Agent Coulson is out of munitions. Game Over
  67. 97.

    FITZ'PLANATION When I first heard about all this 
 I

    f o u n d t h i s co n ce p t to t a l l y awesome and that’s what motivated me to implement it in PHP. At the same time I find coroutines really scary. There is a thin line between awesome code and a total mess and I think coroutines sit exactly on that line. It’s hard for me to say whether writing async code in the way outlined is really beneficial. Nikita Popov @hollodotme THE REAL FITZ
  68. 100.

    RESOURCES • PHP Generator Documentation: https://secure.php.net/manual/en/language.generators.php • VLD extension for

    PHP: https://derickrethans.nl/projects.html#vld • «Introduction to generators» by Niklas Keller: https://blog.kelunik.com/2017/09/14/an-introduction-to-generators-in-php.html • PHP 5.5 migration guide: https://secure.php.net/manual/en/migration55.new-features.php • «Cooperative multitasking using coroutines (in PHP!)» by Nikita Popov
 https://nikic.github.io/2012/12/22/Cooperative-multitasking-using-coroutines-in-PHP.html • Repo with executable code of this talk:
 https://github.com/hollodotme/talk-preps/tree/master/agents-of-yield • Definition of Coroutines on Stack Overflow:
 https://stackoverflow.com/questions/553704/what-is-a-coroutine • Definition of Cooperative Multitasking on wikipedia.org: 
 https://en.wikipedia.org/wiki/Cooperative_multitasking @hollodotme