Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Turn on the Generator!

Turn on the Generator!

Zombie apocalypses are the worst. Low on food, low on water, low on power. The worst part about zombie apocalypses are the waiting. But we can prepare for that…

This is a talk about the new yield keyword. How it works, what it’s good for, and how to make the most of it. We’ll unpack the structure of a generator, and replace some old parts of our application with new, efficient and wonderfully readable code.

Join me as we fight off the hungry hordes, and unlock the true power hidden in PHP generators.

Christopher Pitt

April 22, 2016
Tweet

More Decks by Christopher Pitt

Other Decks in Technology

Transcript

  1. View Slide

  2. View Slide

  3. View Slide

  4. TURN ON
    THE GENERATOR

    View Slide

  5. ARRAYS
    $coordinates = [
    [039.842286, -108.457031],
    [032.916485, -113.906250],
    [033.431441, -099.404297],
    [042.032974, -097.031250],
    [050.064192, -106.962891],
    ];
    foreach ($coordinates as $coordinate) {
    CDC::innoculate($coordinate);
    }
    print "innoculate " . count($coordinates) . " places";
    $filter = function($coordinate) {
    return $coordinate[1] < -98;
    };
    $breakouts = array_filter($coordinates, $filter);

    View Slide

  6. THINGS LIKE ARRAYS
    $secret = new DomDocument();
    $secret->loadHTMLFile(
    "secure/2021/lockdown-protocols.html"
    );
    $steps = $secret->getElementsByTagName("h1");
    foreach ($steps as $step) {
    Military::follow($step);
    }
    print "followed " . $steps->length . " steps";

    View Slide

  7. THINGS LIKE ARRAYS
    is_array($coordinates) // 㱺 true
    is_array($steps) // 㱺 false
    $coordinates instanceof Traversable // 㱺 error
    $steps instanceof Traversable // 㱺 true

    View Slide

  8. THINGS LIKE ARRAYS
    class ContainmentProtocols
    implements Traversable {
    } // 㱺 error

    View Slide

  9. ITERATORS
    class EvacuationProcedures
    implements Iterator {
    function current();
    function key();
    function next();
    function rewind();
    function valid();
    }

    View Slide

  10. ITERATORS
    class EvacuationProcedures
    implements Iterator {
    /**
    * Returns the current value
    * where the array is at
    */
    function current();
    function key();
    function next();
    function rewind();
    function valid();
    }

    View Slide

  11. ITERATORS
    class EvacuationProcedures
    implements Iterator {
    function current();
    /**
    * Returns the pointer to
    * the current position
    */
    function key();
    function next();
    function rewind();
    function valid();
    }

    View Slide

  12. ITERATORS
    class EvacuationProcedures
    implements Iterator {
    function current();
    function key();
    /**
    * Advances the pointer to
    * the next position
    */
    function next();
    function rewind();
    function valid();
    }

    View Slide

  13. ITERATORS
    class EvacuationProcedures
    implements Iterator {
    function current();
    function key();
    function next();
    /**
    * Moves the pointer back
    * to the beginning
    */
    function rewind();
    function valid();
    }

    View Slide

  14. ITERATORS
    class EvacuationProcedures
    implements Iterator {
    function current();
    function key();
    function next();
    function rewind();
    /**
    * Indicates whether there
    * are more values to go
    */
    function valid();
    }

    View Slide

  15. ABSTRACTION

    View Slide

  16. ABSTRACTION
    function numberForPatient($patient) {
    return Registry::getNewNumber($patient);
    }
    function admitPatients(array $patients) {
    $numbers = array_map(
    "numberForPatient", $patients
    );
    for ($i = 0; $i < count($patients); $i++) {
    Station::assignPatientToRoom(
    $numbers[$i], $patients[$i]
    );
    }
    }

    View Slide

  17. ABSTRACTION
    class Patients
    implements Iterator {
    // ...
    function key() {
    return $this->patients[
    $this->pointer
    ];
    }
    function current() {
    return numberForPatient(
    $this->key()
    );
    }
    }

    View Slide

  18. ABSTRACTION
    $intake = [
    "Cathy",
    "Cal",
    "Joe",
    ];
    $patients = new Patients($intake);
    foreach ($patients as $patient => $number) {
    Station::assignPatientToRoom(
    $number, $patient
    );
    }

    View Slide

  19. ABSTRACTION
    class ClassifiedPatients
    implements Iterator {
    // ...construct($patients)
    function key() {
    return md5(
    $this->patients->key()
    );
    }
    }

    View Slide

  20. ABSTRACTION
    $intake = [
    "Cathy",
    "Cal",
    "Joe",
    ];
    $patients = new ClassifiedPatients(
    new Patients($intake)
    );
    foreach ($patients as $patient => $number) {
    Station::assignPatientToRoom(
    $number, $patient
    );
    }

    View Slide

  21. ABSTRACTION
    class SearchablePatients
    implements IteratorAggregate {
    // ...construct($patients)
    function getIterator() {
    return $this->patients;
    }
    function searchFor($patient) {
    $i = 0;
    for ($this->patients as $next => $number) {
    if ($patient == $next) {
    return $i;
    }
    $i++;
    }
    }
    }

    View Slide

  22. ABSTRACTION
    class CountablePatients
    implements IteratorAggregate, Countable {
    // ...construct($patients)
    function count() {
    return iterator_count($this->patients);
    }
    }
    $countable = new CountablePatients(
    new Patients($intake)
    );
    count($countable); // 㱺 3

    View Slide

  23. ABSTRACTION
    class SeekablePatients
    implements IteratorAggregate, SeekableIterator {
    // ...construct($patients)
    function seek($position) {
    $i = 0;
    foreach ($this->patients as $patient) {
    if ($i++ == $position) {
    return;
    }
    }
    }
    }
    $seekable = new SeekablePatients(
    new Patients($intake)
    );
    $seekable
    ->seek(1)
    ->current(); // 㱺 "Cal"

    View Slide

  24. ABSTRACTION
    class ArrayablePatients
    implements IteratorAggregate, ArrayAccess {
    // ...construct($patients)
    function offsetExists($offset);
    function offsetGet($offset);
    function offsetSet($offset, $value);
    function offsetUnset($offset);
    }
    $array = new ArrayablePatients(
    new Patients($intake)
    );
    $array[1]; // 㱺 "Cal"

    View Slide

  25. ABSTRACTION
    • AppendIterator
    • ArrayIterator
    • CachingIterator
    • CallbackFilterIterator
    • DirectoryIterator
    • EmptyIterator
    • FilesystemIterator
    • FilterIterator
    • GlobIterator

    View Slide

  26. ABSTRACTION
    • InfiniteIterator
    • IteratorIterator
    • LimitIterator
    • Mul0pleIterator
    • NoRewindIterator
    • ParentIterator
    • RecursiveArrayIterator
    • RecursiveCachingIterator
    • RecursiveCallbackFilterIterator

    View Slide

  27. ABSTRACTION
    • RecursiveDirectoryIterator
    • RecursiveFilterIterator
    • RecursiveIteratorIterator
    • RecursiveRegexIterator
    • RecursiveTreeIterator
    • RegexIterator

    View Slide

  28. PROBLEMS
    $patients = new Patients($intake);
    array_filter($patients, function($number) {
    // 㱺 error
    });

    View Slide

  29. PROBLEMS
    // github.com/nikic/iter
    $patients = new Patients($intake);
    iter\filter(function($number) {
    // 㱺 ok
    }, $patients);

    View Slide

  30. PROBLEMS
    // github.com/nikic/iter
    $patients = new Patients($intake);
    iter\filter(function($number) {
    // 㱺 ok
    }, $patients);

    View Slide

  31. View Slide

  32. View Slide

  33. ABSTRACTION

    View Slide

  34. ABSTRACTION
    $instructions = "";
    $chapters = file_get_contents(
    "delta/operating-manual.txt"
    );
    foreach ($chapters as $chapter) {
    $instructions .= file_get_contents(
    "delta/operating-manual/" . $chapter
    );
    }
    print "instructions: \n\n" . $instructions;
    print "you've escaped!";

    View Slide

  35. ABSTRACTION
    class OperatingManual
    implements Iterator {
    private $chapters;
    function __construct($file) {
    $this->chapters = file_get_contents(
    $file
    );
    }
    }

    View Slide

  36. ABSTRACTION
    class OperatingManual
    implements Iterator {
    // ...
    private $chapter = 0;
    function rewind() {
    $this->chapter = 0;
    }
    function next() {
    $this->chapter++;
    }
    }

    View Slide

  37. ABSTRACTION
    class OperatingManual
    implements Iterator {
    // ...
    function key() {
    return $this->chapter[
    $this->chapter
    ];
    }
    function current() {
    return file_get_contents(
    $this->key()
    );
    }
    function valid() {
    isset(
    $this->chapters[
    $this->chapter
    ]
    );
    }
    }

    View Slide

  38. ABSTRACTION
    $manual = new OperatingManual(
    "delta/operating-manual.txt"
    );
    foreach ($manual as $chapter) {
    print "next chapter: \n\n" . $chapter;
    }
    print "you've escaped!";

    View Slide

  39. View Slide

  40. View Slide

  41. View Slide

  42. ABSTRACTION
    $manual = new OperatingManual(
    "delta/operating-manual.txt"
    );
    foreach ($manual as $chapter) {
    print "next chapter: \n\n" . $chapter;
    }
    print "you've escaped!";

    View Slide

  43. ITERATOR LIKE THINGS
    function OperatingManual($file) {
    $handle = fopen($file, "r");
    while (!feof($handle)) {
    yield file_get_contents(
    trim(fgets($handle))
    );
    }
    fclose($handle);
    }
    $manual = OperatingManual(
    "delta/operating-manual.txt"
    );

    View Slide

  44. ITERATOR LIKE THINGS
    print_r(get_class_methods($manual));
    // 㱺 Array
    // (
    // [0] => rewind
    // [1] => valid
    // [2] => current
    // [3] => key
    // [4] => next
    // [5] => send
    // [6] => throw
    // [7] => getReturn
    // [8] => __wakeup
    // )

    View Slide

  45. View Slide

  46. ARRAYS ARE
    LIKE ROLODEXES

    View Slide

  47. ASK FOR ITEM #3,
    AND YOU'LL GET ITEM #3

    View Slide

  48. IT'S PREDICTABLE

    View Slide

  49. FUNCTIONS ACT LIKE
    DATA TABLES

    View Slide

  50. PUT THE SAME
    THING IN...

    View Slide

  51. ...YOU GET THE
    SAME THING OUT

    View Slide

  52. IT'S PREDICTABLE

    View Slide

  53. NULL OBJECT PATTERN

    View Slide

  54. NULL OBJECT PATTERN
    function arrayToObject(array $data) {
    $encoded = json_encode($data);
    return json_decode($encoded);
    }
    $report = arrayToObject([
    "station" => [
    "armory" => [
    "safe" => [
    "weapons" => "one rifle",
    ],
    ],
    ],
    ]);

    View Slide

  55. NULL OBJECT PATTERN
    function searchForWeapons($report) {
    return $report
    ->station
    ->armory
    ->safe
    ->weapons;
    }
    print searchForWeapons(
    arrayToObject(["station" => null])
    );
    // 㱺 error: accessing $armory on null

    View Slide

  56. NULL OBJECT PATTERN
    function searchForWeapons($report) {
    if ($station = $report->station) {
    if ($armory = $station->armory) {
    // ...
    }
    }
    }

    View Slide

  57. NULL OBJECT PATTERN
    class Maybe {
    private $value;
    function __construct($value) {
    $this->value = $value;
    }
    function __get($property) {
    if (is_object($this->value)) {
    return new static(
    $this->value->$property
    );
    }
    }
    function value() {
    return $this->value;
    }
    }

    View Slide

  58. NULL OBJECT PATTERN
    function searchForWeapons($report) {
    return $report
    ->station
    ->armory
    ->safe
    ->weapons
    ->value();
    }
    print searchForWeapons(
    arrayToObject(["station" => null]);
    );
    // 㱺 null

    View Slide

  59. NULL OBJECT PATTERN
    static function from($value) {
    if ($value instanceof static) {
    return $value;
    }
    return new static($value);
    }
    static function generator($starting, $generator) {
    $monad = static::from($starting);
    $generator = $generator($monad);
    $next = $generator->current();
    while ($generator->valid()) {
    $monad = static::from($next);
    $next = $generator->send($monad);
    }
    return $generator->getReturn();
    }

    View Slide

  60. NULL OBJECT PATTERN
    $weapons = Maybe::generator(
    $report, function($report) {
    $station = yield $report->station;
    $armory = yield $station->armory;
    $safe = yield $armory->safe;
    return $safe->weapons;
    }
    );
    print $weapons;

    View Slide

  61. COROUTINES

    View Slide

  62. COROUTINES
    $people = [
    "Lorna",
    "Rob",
    "Chris",
    "Aaron",
    "Sara",
    ];
    $fences = [
    "next to the old car",
    "behind the kennel",
    "out back",
    ];

    View Slide

  63. COROUTINES
    function checkRations(array $people) {
    foreach ($people as $person) {
    print "checking rations for " . $person;
    yield showRationsFor($person);
    }
    }
    function watchFences(array $people, array $fences) {
    foreach ($fences as $fence) {
    $next = next($people);
    if ($next) {
    $next = reset($people);
    }
    print $next . " is watching " . $fence;
    yield watchFence($next, $fence);
    }
    }

    View Slide

  64. COROUTINES
    class Task {
    private $generator;
    function __construct($generator) {
    $this->generator = $generator;
    }
    function run() {
    $this->generator->next();
    }
    function valid() {
    return $this->generator->valid();
    }
    }

    View Slide

  65. COROUTINES
    class Manager {
    private $tasks = [];
    function addTask(Task $task) {
    array_unshift($this->tasks, $task);
    }
    function run() {
    while (count($this->tasks)) {
    $next = array_pop($this->tasks);
    $next->run();
    if ($next->valid()) {
    $this->addTask($next);
    }
    }
    }
    }

    View Slide

  66. COROUTINES
    $manager = new Manager();
    $manager->addTask(
    new Task(checkRations($people)
    );
    $manager->addTask(
    new Task(watchFences($people, $fences)
    );
    $manager->run();
    // 㱺 checking rations for Lorna
    // 㱺 Lorna is watching next to the old car
    // 㱺 checking rations for Rob
    // 㱺 Rob is watching behind the kennel
    // 㱺 checking rations for Chris
    // 㱺 Chris is watching out back
    // 㱺 checking rations for Aaron
    // 㱺 checking rations for Sara

    View Slide

  67. COROUTINES
    $manager = new Manager();
    $manager->addTask(
    new Task(checkRations($people)
    );
    $manager->addTask(
    new Task(watchFences($people, $fences)
    );
    $manager->run();
    // 㱺 checking rations for Lorna
    // 㱺 Lorna is watching next to the old car
    // 㱺 checking rations for Rob
    // 㱺 Rob is watching behind the kennel
    // 㱺 checking rations for Chris
    // 㱺 Chris is watching out back
    // 㱺 checking rations for Aaron
    // 㱺 checking rations for Sara

    View Slide

  68. View Slide

  69. View Slide