Generating
Power with Yield/Jason Myers @jasonamyers
Yield, a modern language love
story
Originally Proposed in 1995, the yield keyword became official via the
RFP on June 20th 2013 with PHP 5.5.Generators
Facebook said a Hip, Hop and Don't Stop, and did their own yield
generators in HipHop PHP
They are HEAVILY BASED off of Python, with a nod towards the
Mozilla JS implementation and the await C# concept.
Iterator
An object that lets us traverse a container
PHP Iterator Interface
Iterator extends Traversable {
/* Methods */
abstract public mixed current ( void )
abstract public scalar key ( void )
abstract public void next ( void )
abstract public void rewind ( void )
abstract public boolean valid ( void )
}
For example checkout classArrayIterator
ArrayIterator Example
$fruits = array(
"apple" => "yummy",
"orange" => "ah ya, nice",
"grape" => "wow, I love it!",
"plum" => "nah, not me"
);
$obj = new ArrayObject( $fruits );
$it = $obj->getIterator();
echo "Iterating over: " . $obj->count() . " valuesn";
while( $it->valid() )
{
echo $it->key() . "=" . $it->current() . "n";
$it->next();
}
Iterating over: 4 values
apple=yummy
orange=ah ya, nice
grape=wow, I love it!
plum=nah, not me
Generator
a special routine that can be used to control the iteration behavior of
a loop, and yields the values one at a time
TL;DR
A generator looks like a function but behaves like an iterator
Performant?
range(0, 1000000)
Uses over 100MB of RAM
Generator Version
function xrange($start, $limit, $step = 1) {
if ($start < $limit) {
if ($step <= 0) {
throw new LogicException('Step must be +ve');
}
for ($i = $start; $i <= $limit; $i += $step) {
yield $i;
}
} else {
if ($step >= 0) {
throw new LogicException('Step must be -ve');
}
for ($i = $start; $i >= $limit; $i += $step) {
yield $i;
}
}
}
uses less than 1KB!
TL; DR
function xrange($min, $max) {
for ($i = $min; $i < $max; $i++) {
yield $i;
}
}
Sequences
function collatz($val) {
yield $val;
while ($val != 1) {
if ($val%2 == 0) {
$val /= 2;
} else {
$val = 3*$val + 1;
}
yield $val;
}
}
foreach (collatz(11) as $c) {
echo $c," ";
}
11 34 17 52 26 13 40 20 10 5 16 8 4 2 1
Y U Only LOOPin?
It will work for any function that takes an Iterator or a Traversable as
argument
$arr = iterator_to_array(collatz(11));
Transformations
function multiply_sequence($a, $fac) {
foreach ($a as $val) {
yield $val*$fac;
}
}
function to_html_list($input) {
foreach ($input as $val) {
yield "<li>".$val."</li>";
}
}
Chaining
foreach (to_html_list(multiply_sequence(collatz(5),2)) as $val) {
echo $val,"n";
}
<li>10</li>
<li>32</li>
<li>16</li>
<li>8</li>
<li>4</li>
<li>2</li>
Selections
function select_pattern($input, $pattern) {
foreach ($input as $val) {
if (preg_match($pattern, $val)) {
yield $val;
}
}
}
Breath In
function getLines($file) {
$f = fopen($file, 'r');
if (!$f) {
throw new Exception();
}
while ($line = fgets($f)) {
yield $line;
}
fclose($f);
}
foreach (getLines("someFile") as $line) {
doSomethingWithLine($line);
}
Breath Out
function createLog($file) {
$f = fopen($file, 'a');
while (true) {
$line = yield;
fwrite($f, $line);
}
}
$log = createLog($file);
$log->send("First");
$log->send("Second");
$log->send("Third");
Bro Remote Me!
Fake the simultaneous processing of data
Green Threads
threads that are scheduled by a virtual machine (VM/interperter?)
instead of natively by the underlying operating system
function step1() {
$f = fopen("file.txt", 'r');
while ($line = fgets($f)) {
processLine($line);
yield true;
}
}
function step2() {
$f = fopen("file2.txt", 'r');
while ($line = fgets($f)) {
processLine($line);
yield true;
}
}
function step3() {
$f = fsockopen("www.example.com", 80);
stream_set_blocking($f, false);
$headers = "GET / HTTP/1.1rn";
$headers .= "Host: www.example.comrn";
$headers .= "Connection: Closernrn";
fwrite($f, $headers);
$body = '';
while (!feof($f)) {
$body .= fread($f, 8192);
yield true;
}
processBody($body);
}
function runner(array $steps) {
while (true) {
foreach ($steps as $key => $step) {
$step->next();
if (!$step->valid()) {
unset($steps[$key]);
}
}
if (empty($steps)) return;
}
}
runner(array(step1(), step2(), step3()));
ZOMG... THERE BE DRAGONS!
This relies on making sure we have no blocking IO
overREACTPHP much?
event based, non-blocking IO - ReActPHP
One More Thing
So if I can flip control, I can haz an Async?
class Buffer {
protected $reads, $data;
public function __construct() {
$this->reads = new SplQueue();
$this->data = new SplQueue();
}
public function read() {
if( $this->data->isEmpty() ) {
$deferred = new ReactPromiseDeferred();
$this->reads->enqueue($deferred->resolver());
return $deferred->promise();
} else {
return ReactPromiseWhen::resolve($this->data->dequeue());
}
}
public function write($str) {
if( $this->reads->isEmpty() ) {
$this->data->enqueue($str);
} else {
$this->reads->dequeue()->resolve($str);
}
}
}
function printer(Buffer $buffer) {
while( true ) {
$value = ( yield Util::async($buffer->read()) );
echo "Printer: ", $value, PHP_EOL;
yield Util::async(nested_printer($buffer));
}
}
function nested_printer(Buffer $buffer) {
for( $i = 0; $i < 5; $i++ ) {
// Yield a promise task and wait for the result - this is non-blocking
$value = ( yield Util::async($buffer->read()) );
echo "Nested printer: ", $value, PHP_EOL;
}
}
$buffer = new Buffer();
$scheduler = new AsyncScheduler();
$scheduler->add(new AsyncTaskGeneratorTask(printer($buffer)));
$i = 0;
$scheduler->add(new AsyncTaskRecurringTask(
function() use($buffer, &$i) { $buffer->write(++$i); }
));
$scheduler->run();
Printer: 1
Nested printer: 2
Nested printer: 3
Nested printer: 4
Nested printer: 5
Nested printer: 6
Printer: 7
Nested printer: 8
Nested printer: 9
Nested printer: 10
Nested printer: 11
Nested printer: 12
...
$loop = ReactEventLoopFactory::create();
$scheduler = new AsyncScheduler();
$scheduler->add(new AsyncTaskRecurringTask([$loop, 'tick']));
$scheduler->run();
Async
Created by Matt Pryor, on Bitbucket
Thanks
Huge thanks to Paul M. Jones and William Golden!
THE END
@jasonamyers

Generating Power with Yield

  • 1.
  • 2.
    Yield, a modernlanguage love story Originally Proposed in 1995, the yield keyword became official via the RFP on June 20th 2013 with PHP 5.5.Generators Facebook said a Hip, Hop and Don't Stop, and did their own yield generators in HipHop PHP
  • 3.
    They are HEAVILYBASED off of Python, with a nod towards the Mozilla JS implementation and the await C# concept.
  • 6.
    Iterator An object thatlets us traverse a container
  • 8.
    PHP Iterator Interface Iteratorextends Traversable { /* Methods */ abstract public mixed current ( void ) abstract public scalar key ( void ) abstract public void next ( void ) abstract public void rewind ( void ) abstract public boolean valid ( void ) } For example checkout classArrayIterator
  • 9.
    ArrayIterator Example $fruits =array( "apple" => "yummy", "orange" => "ah ya, nice", "grape" => "wow, I love it!", "plum" => "nah, not me" ); $obj = new ArrayObject( $fruits ); $it = $obj->getIterator(); echo "Iterating over: " . $obj->count() . " valuesn"; while( $it->valid() ) { echo $it->key() . "=" . $it->current() . "n"; $it->next(); } Iterating over: 4 values apple=yummy orange=ah ya, nice grape=wow, I love it! plum=nah, not me
  • 10.
    Generator a special routinethat can be used to control the iteration behavior of a loop, and yields the values one at a time
  • 11.
    TL;DR A generator lookslike a function but behaves like an iterator
  • 13.
  • 15.
    Generator Version function xrange($start,$limit, $step = 1) { if ($start < $limit) { if ($step <= 0) { throw new LogicException('Step must be +ve'); } for ($i = $start; $i <= $limit; $i += $step) { yield $i; } } else { if ($step >= 0) { throw new LogicException('Step must be -ve'); } for ($i = $start; $i >= $limit; $i += $step) { yield $i; } } } uses less than 1KB!
  • 16.
    TL; DR function xrange($min,$max) { for ($i = $min; $i < $max; $i++) { yield $i; } }
  • 17.
    Sequences function collatz($val) { yield$val; while ($val != 1) { if ($val%2 == 0) { $val /= 2; } else { $val = 3*$val + 1; } yield $val; } } foreach (collatz(11) as $c) { echo $c," "; } 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1
  • 19.
    Y U OnlyLOOPin? It will work for any function that takes an Iterator or a Traversable as argument $arr = iterator_to_array(collatz(11));
  • 20.
    Transformations function multiply_sequence($a, $fac){ foreach ($a as $val) { yield $val*$fac; } } function to_html_list($input) { foreach ($input as $val) { yield "<li>".$val."</li>"; } }
  • 21.
    Chaining foreach (to_html_list(multiply_sequence(collatz(5),2)) as$val) { echo $val,"n"; } <li>10</li> <li>32</li> <li>16</li> <li>8</li> <li>4</li> <li>2</li>
  • 22.
    Selections function select_pattern($input, $pattern){ foreach ($input as $val) { if (preg_match($pattern, $val)) { yield $val; } } }
  • 23.
    Breath In function getLines($file){ $f = fopen($file, 'r'); if (!$f) { throw new Exception(); } while ($line = fgets($f)) { yield $line; } fclose($f); } foreach (getLines("someFile") as $line) { doSomethingWithLine($line); }
  • 24.
    Breath Out function createLog($file){ $f = fopen($file, 'a'); while (true) { $line = yield; fwrite($f, $line); } } $log = createLog($file); $log->send("First"); $log->send("Second"); $log->send("Third");
  • 26.
    Bro Remote Me! Fakethe simultaneous processing of data
  • 28.
    Green Threads threads thatare scheduled by a virtual machine (VM/interperter?) instead of natively by the underlying operating system
  • 29.
    function step1() { $f= fopen("file.txt", 'r'); while ($line = fgets($f)) { processLine($line); yield true; } }
  • 30.
    function step2() { $f= fopen("file2.txt", 'r'); while ($line = fgets($f)) { processLine($line); yield true; } }
  • 31.
    function step3() { $f= fsockopen("www.example.com", 80); stream_set_blocking($f, false); $headers = "GET / HTTP/1.1rn"; $headers .= "Host: www.example.comrn"; $headers .= "Connection: Closernrn"; fwrite($f, $headers); $body = ''; while (!feof($f)) { $body .= fread($f, 8192); yield true; } processBody($body); }
  • 32.
    function runner(array $steps){ while (true) { foreach ($steps as $key => $step) { $step->next(); if (!$step->valid()) { unset($steps[$key]); } } if (empty($steps)) return; } } runner(array(step1(), step2(), step3()));
  • 33.
    ZOMG... THERE BEDRAGONS! This relies on making sure we have no blocking IO
  • 34.
    overREACTPHP much? event based,non-blocking IO - ReActPHP
  • 35.
    One More Thing Soif I can flip control, I can haz an Async?
  • 36.
    class Buffer { protected$reads, $data; public function __construct() { $this->reads = new SplQueue(); $this->data = new SplQueue(); } public function read() { if( $this->data->isEmpty() ) { $deferred = new ReactPromiseDeferred(); $this->reads->enqueue($deferred->resolver()); return $deferred->promise(); } else { return ReactPromiseWhen::resolve($this->data->dequeue()); } } public function write($str) { if( $this->reads->isEmpty() ) { $this->data->enqueue($str); } else { $this->reads->dequeue()->resolve($str); } } }
  • 37.
    function printer(Buffer $buffer){ while( true ) { $value = ( yield Util::async($buffer->read()) ); echo "Printer: ", $value, PHP_EOL; yield Util::async(nested_printer($buffer)); } }
  • 38.
    function nested_printer(Buffer $buffer){ for( $i = 0; $i < 5; $i++ ) { // Yield a promise task and wait for the result - this is non-blocking $value = ( yield Util::async($buffer->read()) ); echo "Nested printer: ", $value, PHP_EOL; } }
  • 39.
    $buffer = newBuffer(); $scheduler = new AsyncScheduler(); $scheduler->add(new AsyncTaskGeneratorTask(printer($buffer))); $i = 0; $scheduler->add(new AsyncTaskRecurringTask( function() use($buffer, &$i) { $buffer->write(++$i); } )); $scheduler->run(); Printer: 1 Nested printer: 2 Nested printer: 3 Nested printer: 4 Nested printer: 5 Nested printer: 6 Printer: 7 Nested printer: 8 Nested printer: 9 Nested printer: 10 Nested printer: 11 Nested printer: 12 ...
  • 40.
    $loop = ReactEventLoopFactory::create(); $scheduler= new AsyncScheduler(); $scheduler->add(new AsyncTaskRecurringTask([$loop, 'tick'])); $scheduler->run();
  • 41.
    Async Created by MattPryor, on Bitbucket
  • 42.
    Thanks Huge thanks toPaul M. Jones and William Golden!
  • 43.