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. AUGUST 22, 2018 #MEETUP @PHPUGDD

  2. None
  3. github.com/hollodotme @hollodotme EXECUTIVE PRODUCER holger woltersdorf

  4. github.com/hollodotme @hollodotme@phpc.social EXECUTIVE PRODUCER holger woltersdorf

  5. CHAPTER 1: GENERATORS @hollodotme

  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
  7. FITZ'PLANATION BOY, WHAT THE HELL ARE YOU TALKING ABOUT? @hollodotme

  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  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
  20. AND… GENERATORS IMPLEMENT IT… @hollodotme

  21. AND… GENERATORS IMPLEMENT IT… ITE… @hollodotme

  22. AND… GENERATORS IMPLEMENT IT… ITE… ITERATOR! @hollodotme

  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
  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
  25. OH… GOOD TO SEE SOMETHING FAMILIAR
 -
 SOMETHING I CAN

    COUNT ON! @hollodotme
  26. BUT YOU CAN’T, PHIL. @hollodotme

  27. IT’S NOT COUNTABLE! @hollodotme

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

    Warning: count(): Parameter must be an array or an object that implements Countable @hollodotme
  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
  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
  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
  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
  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
  34. What the F***?! @hollodotme

  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
  36. YOU KNOW, IT'S LIKE ... @hollodotme

  37. GONE. @hollodotme

  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
  39. YIELD FROM … @hollodotme

  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
  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
  42. YIELD ASS=>OC @hollodotme

  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
  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
  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
  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
  47. YIELD & RETURN @hollodotme

  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
  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
  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
  51. PSA: PHP >=7.0 ONLY @hollodotme

  52. UPGRADE YOUR STUFF! @hollodotme

  53. SERIOUSLY @hollodotme

  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
  55. THIS IS A NICE CAR! @hollodotme

  56. Mack, 
 CAN WE MAKE IT FLY? @hollodotme

  57. CHAPTER 2: COROUTINES @hollodotme

  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
  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
  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
  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
  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
  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
  64. JEMMA'LOC Sent: Foo (1) Sent: Bar (2) @hollodotme

  65. @hollodotme

  66. WHAT HAPPENED TO ZERO? @hollodotme

  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
  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
  69. JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); print $gen!->send( '' ) . PHP_EOL; @hollodotme
  70. JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); print $gen!->send( '' ) . PHP_EOL; — bar @hollodotme
  71. JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); $gen!->rewind(); print $gen!->send( '' ) . PHP_EOL; @hollodotme
  72. JEMMA'LOC function gen() { yield 'foo'; yield 'bar'; } $gen

    = gen(); $gen!->rewind(); print $gen!->send( '' ) . PHP_EOL; — bar @hollodotme
  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
  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
  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
  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
  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
  78. JEMMA'LOC function gen() { yield 1; } $generator = gen();

    $generator!->!__wakeup(); @hollodotme
  79. JEMMA'LOC function gen() { yield 1; } $generator = gen();

    $generator!->!__wakeup(); — PHP Fatal error: Uncaught Exception: Unserialization of 'Generator' is not allowed @hollodotme
  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!
  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
  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
  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
  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
  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
  86. TAHITI? @hollodotme

  87. CHAPTER 3: COOPERATIVE MULTITASKING @hollodotme

  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
  89. THE FRAMEWORK (Task Scheduler) AGENTS OF YIELD (Coroutines) SystemCalls (Hooks

    into the Coroutines) Ammunition (Limits)
  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
  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
  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
  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
  94. THE FINAL BATTLE @hollodotme

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

    $battleGround!->newAgent( 'Mack', agent( 5 ) ); $battleGround!->fight(); print 'Game Over' . PHP_EOL; @hollodotme
  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
  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
  98. THE END @hollodotme

  99. github.com/hollodotme @hollodotme THANK YOU! holger woltersdorf

  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