$30 off During Our Annual Pro Sale. View Details »

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 cooporative multitasking. Coincidental movie analogies included.

Holger Woltersdorf

August 25, 2018
Tweet

More Decks by Holger Woltersdorf

Other Decks in Programming

Transcript

  1. AUGUST 25, 2018
    #FROSCON-PHP-TRACK

    View Slide

  2. View Slide

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

    View Slide

  4. github.com/hollodotme
    @[email protected]
    EXECUTIVE PRODUCER
    holger woltersdorf

    View Slide

  5. CHAPTER 1: GENERATORS
    @hollodotme

    View Slide

  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

    View Slide

  7. FITZ'PLANATION
    BOY, WHAT
    THE HELL
    ARE YOU
    TALKING
    ABOUT?
    @hollodotme

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  20. AND…
    GENERATORS
    IMPLEMENT
    IT…
    @hollodotme

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  25. OH…
    GOOD TO SEE
    SOMETHING
    FAMILIAR

    -

    SOMETHING I
    CAN COUNT ON!
    @hollodotme

    View Slide

  26. BUT YOU CAN’T,
    PHIL.
    @hollodotme

    View Slide

  27. IT’S NOT
    COUNTABLE!
    @hollodotme

    View Slide

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

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  34. What the
    F***?!
    @hollodotme

    View Slide

  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

    View Slide

  36. YOU KNOW,
    IT'S LIKE ...
    @hollodotme

    View Slide

  37. GONE.
    @hollodotme

    View Slide

  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

    View Slide

  39. YIELD FROM …
    @hollodotme

    View Slide

  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

    View Slide

  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

    View Slide

  42. YIELD ASS=>OC
    @hollodotme

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  47. YIELD & RETURN
    @hollodotme

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  51. PSA:
    PHP >=7.0
    ONLY
    @hollodotme

    View Slide

  52. UPGRADE
    YOUR
    STUFF!
    @hollodotme

    View Slide

  53. SERIOUSLY
    @hollodotme

    View Slide

  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

    View Slide

  55. WELL, NICE!
    @hollodotme

    View Slide

  56. ANY MORE FEATURES?
    @hollodotme

    View Slide

  57. CHAPTER 2: COROUTINES
    @hollodotme

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  64. JEMMA'LOC
    Sent: Foo (1)
    Sent: Bar (2)
    @hollodotme

    View Slide

  65. @hollodotme

    View Slide

  66. WHAT
    HAPPENED
    TO ZERO?
    @hollodotme

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    bar
    @hollodotme

    View Slide

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

    View Slide

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

    bar
    @hollodotme

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    PHP Fatal error: Uncaught Exception:
    Unserialization of 'Generator' is not allowed
    @hollodotme

    View Slide

  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!

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  86. TAHITI?
    @hollodotme

    View Slide

  87. CHAPTER 3:
    COOPERATIVE MULTITASKING
    @hollodotme

    View Slide

  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

    View Slide

  89. THE FRAMEWORK
    (Task Scheduler)
    AGENTS OF YIELD
    (Coroutines)
    SystemCalls
    (Hooks into the
    Coroutines)
    Ammunition
    (Limits)

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  94. THE FINAL BATTLE
    @hollodotme

    View Slide

  95. $battleGround = new TheFramework();
    $battleGround!->newAgent( 'Coulson', agent( 12 ) );
    $battleGround!->newAgent( 'Mack', agent( 5 ) );
    $battleGround!->fight();
    print 'Game Over' . PHP_EOL;
    @hollodotme

    View Slide

  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

    View Slide

  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

    View Slide

  98. THE END
    @hollodotme

    View Slide

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

    View Slide

  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

    View Slide