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

Monads In PHP → php[tek]

Monads In PHP → php[tek]

Many developers get lost in the hype of object oriented design. They miss out on how expressive and succinct their code could be if they tried functional programming.

Take Monads, for instance. Many developers haven't even heard the name, much less are able to describe what Monads are and how they can be useful in every-day code.

In this talk, we'll gain a clear and simple understanding of what Monads are, and how they can help us to refactor our code to be clear and concise.

Christopher Pitt

May 27, 2016
Tweet

More Decks by Christopher Pitt

Other Decks in Technology

Transcript

  1. View Slide

  2. MONADS in PHP

    View Slide

  3. Thanks to PHP[TEK]

    View Slide

  4. Thanks to SILVERSTRIPE

    View Slide

  5. My GOAL

    View Slide

  6. What is a STACK?

    View Slide

  7. OPERATIONS and RULES

    View Slide

  8. OPERATIONS
    ▸ Stack::push(mixed $value): Stack
    ▸ Stack::pop(): Stack
    ▸ Stack::top(): mixed
    ▸ Stack::isEmpty(): bool

    View Slide

  9. RULES
    ▸ $stack->push($value)->top() == $value
    ▸ $stack->push($value)->pop() == $stack
    ▸ $stack->push($value)->isEmpty() == false
    ▸ (new Stack)->isEmpty() == true

    View Slide

  10. we can implement these
    operations however we like

    View Slide

  11. class ArrayStack {
    private $values;
    public function __construct(array $values = []) {
    $this->values = $values;
    }

    View Slide

  12. public function push($value) {
    $clone = clone $this;
    array_push($clone->values, $value);
    return $clone;
    }
    public function pop() {
    $clone = clone $this;
    array_pop($clone->values);
    return $clone;
    }

    View Slide

  13. public function top() {
    $count = count($this->values);
    if ($count > 0) {
    return $this->values[$count - 1];
    }
    return null;
    }
    public function isEmpty() {
    return count($this->values) === 0;
    }
    }

    View Slide

  14. class LinkedListStack {
    private $head;
    private $tail;
    public function __construct($head = null, $tail = null) {
    $this->head = $head;
    $this->tail = $tail;
    }

    View Slide

  15. public function push($value) {
    return new static($value, clone $this);
    }
    public function pop() {
    return $this->tail;
    }

    View Slide

  16. public function top() {
    return $this->head;
    }
    public function isEmpty() {
    return $this->tail === null;
    }
    }

    View Slide

  17. we can define more
    operations based on the
    known operations

    View Slide

  18. trait HasSizeMethod {
    public function size() {
    if ($this->isEmpty()) {
    return 0;
    }
    return $this->pop()->size() + 1;
    }
    abstract public function isEmpty();
    abstract public function pop();
    }

    View Slide

  19. class ArrayStack {
    use HasSizeMethod;
    ...
    }
    class LinkedListStack {
    use HasSizeMethod;
    ...
    }

    View Slide

  20. $stack1 = new ArrayStack();
    $stack1->push("one")->push("two")->size(); // 2
    $stack2 = new LinkedListStack();
    $stack2->push("one")->push("two")->size(); // 2

    View Slide

  21. STACK is a SPECIFICATION

    View Slide

  22. COMMON interface

    View Slide

  23. SHARED functionality

    View Slide

  24. what is a COLLECTION?

    View Slide

  25. OPERATIONS and RULES

    View Slide

  26. OPERATIONS
    ▸ Collection::each(callable $func)

    View Slide

  27. RULES
    ▸ Collection::each(callable $func)
    calls a function zero or more times, in immediate sequence

    View Slide

  28. (again) we can implement
    these operations however
    we like

    View Slide

  29. class RangeCollection {
    private $range;
    public function __constuct(array $range = []) {
    $this->range = $range;
    }

    View Slide

  30. public function each(callable $func) {
    foreach ($this->range as $value) {
    $func($value);
    }
    }
    }

    View Slide

  31. class HourIntervalCollection {
    private $start;
    private $end;
    public function __construct($start, $end) {
    $this->start = $start;
    $this->end = $end;
    }

    View Slide

  32. public function each(callable $func) {
    $interval = 60 * 60;
    $next = $this->start + interval;
    while ($next <= $this->end) {
    $func($next);
    $next += interval;
    }
    }
    }

    View Slide

  33. (again) we can define more
    operations based on the
    known operations

    View Slide

  34. trait HasSelectMethod {
    public function select(callable $func) {
    $values = [];
    $this->each(function($value) use ($func, &$values) {
    if ($func($value)) {
    $values[] = $value;
    }
    });
    return $values;
    }
    public abstract function each(callable $func);
    }

    View Slide

  35. class RangeCollection {
    use HasSelectMethod;
    ...
    }
    class HourIntervalCollection {
    use HasSelectMethod;
    ...
    }

    View Slide

  36. function odd($value) {
    return $value % 2 == 0;
    }
    $collection1 = new RangeCollection(range(5, 10));
    $collection1->select("odd"); // [5, 7, 9]
    $collection2 = new HourIntervalCollection(
    strtotime("+0 hours"), strtotime("+5 hours")
    );
    $collection2->select("odd"); // [±1 hours, ±3 hours, ±5 hours]

    View Slide

  37. COLLECTION is a
    SPECIFICATION

    View Slide

  38. COMMON interface

    View Slide

  39. SHARED functionality

    View Slide

  40. what are
    STACK and COLLECTION?

    View Slide

  41. ABSTRACT data types

    View Slide

  42. let's refactor some code!

    View Slide

  43. handling NULL

    View Slide

  44. class Store {
    public $manager;
    }
    class Manager {
    public $address;
    }
    class Address {
    public $city;
    }

    View Slide

  45. $address = new Address();
    $address->city = "St. Louis";
    $manager = new Manager();
    $manager->address = $address;
    $store = new Store();
    $store->manager = $manager;

    View Slide

  46. function getManagerCityForStore($store) {
    return $store->manager->address->city;
    }

    View Slide

  47. $bad = new Store();
    $bad->manage = new Manager();
    print getManagerCityForStore($bad); // error

    View Slide

  48. function getManagerCityForStore($store) {
    if ($store !== null && $store->manager !== null) {
    $manager = $store->manager;
    if ($manager !== null && $manager->address !== null) {
    $address = $manager->address;
    if ($address !== null && $address->city !== null) {
    return $address->city
    }
    }
    }
    }

    View Slide

  49. function getManagerCityForStore($store) {
    if ($store !== null && $store->manager !== null) {
    $manager = $store->manager;
    }
    if ($manager !== null && $manager->address !== null) {
    $address = $manager->address;
    }
    if ($address !== null && $address->city !== null) {
    return $address->city
    }
    }

    View Slide

  50. trait HasTryMethod {
    public function try($property) {
    if ($this->$property !== null) {
    return $this->$property;
    }
    return null;
    }
    }

    View Slide

  51. class Store {
    use HasTryMethod;
    public $manager;
    }
    class Manager {
    use HasTryMethod;
    public $address;
    }
    class Address {
    use HasTryMethod;
    public $city;
    }

    View Slide

  52. function getManagerCityForStore($store) {
    if ($store !== null) {
    $manager = $store->try("manager");
    $address = $manager->try("address");
    $city = $address->try("city");
    return $city;
    }
    }

    View Slide

  53. class Maybe {
    private $value;
    public function __construct($value) {
    if (is_object($value) && $value instanceof static) {
    $value = $value->value();
    }
    $this->value = $value;
    }
    public static function create(...$params) {
    return new static(...$params)
    }

    View Slide

  54. public function try($property) {
    if ($this->value !== null) {
    return $this->value->$property;
    }
    return null;
    }
    public function value() {
    return $this->value;
    }
    }

    View Slide

  55. function getManagerCityForStore($store) {
    $store = Maybe::create($store);
    $manager = Maybe::create($store->try("manager"));
    $address = Maybe::create($manager->try("address"));
    $city = Maybe::create($address->try("city"));
    return $city;
    }

    View Slide

  56. public function try($property) {
    if ($this->value !== null) {
    return static::create($this->value->$property);
    }
    return static::create(null);
    }

    View Slide

  57. function getManagerCityForStore($store) {
    $store = Maybe::create($store);
    $manager = $store->try("manager");
    $address = $manager->try("address");
    $city = $address->try("city");
    return $city;
    }

    View Slide

  58. function getManagerCityForStore($store) {
    return Maybe::create($store)
    ->try("manager")
    ->try("address")
    ->try("city")
    ->value();
    }

    View Slide

  59. public function try(callable $func) {
    if ($this->value !== null) {
    return static::create($func($this->value));
    }
    return static::create(null);
    }

    View Slide

  60. function getManagerCityForStore($store) {
    return Maybe::create($store)
    ->try(function($store) { return $store->manager; })
    ->try(function($manager) { return $manager->address; })
    ->try(function($address) { return $address->city; })
    ->value();
    }

    View Slide

  61. public function then(callable $func) {
    if ($this->value !== null) {
    return static::create($func($this->value));
    }
    return static::create(null);
    }
    public function __call($method) {
    return $this->then(function($value) use ($method) {
    return $value->$method;
    });
    }
    public function __get($property) {
    return $this->$property();
    }

    View Slide

  62. function getManagerCityForStore($store) {
    return Maybe::create($store)
    ->manager
    ->address
    ->city
    ->value();
    }

    View Slide

  63. function getManagerCityForStore($store) {
    return $store->manager->address->city;
    }

    View Slide

  64. handling MANY

    View Slide

  65. class Blog {
    public $categories;
    }
    class Category {
    public $posts;
    }
    class Post {
    public $comments;
    }

    View Slide

  66. $post1 = new Post();
    $post1->comments = ["frist!", "you suck"];
    $post2 = new Post();
    $post2->comments = ["inb4comments", "have this moneys"];
    $category1 = new Category();
    $category1->posts = [$post2];
    $category2 = new Category();
    $category2->posts = [$post1];
    $blog = new Blog();
    $blog->categories = [$category1, $category2];

    View Slide

  67. function map($iterable, callable $func) {
    foreach ($iterable as $value) {
    yield $func($value);
    }
    }
    function flatMap($iterable, callable $func) {
    foreach (map($iterable, $func) as $outer) {
    foreach ($outer as $inner) {
    yield $inner;
    }
    }
    }

    View Slide

  68. function getCommentWordsForBlog($blog) {
    return flatMap($blog->categories, function($category) {
    return flatMap($category->posts, function($post) {
    return flatMap($post->comments, function($comment) {
    return explode(" ", $comment);
    });
    });
    });
    }

    View Slide

  69. class Many {
    private $values;
    public function __construct($values) {
    if (is_object($values) && $values instanceof static) {
    $values = $values->value();
    }
    $this->values = $values;
    }
    public static function create(...$params) {
    return new static(...$params);
    }

    View Slide

  70. public function then(callable $func) {
    return static::create(
    flatMap($this->values, $func)
    );
    }
    public function value() {
    return $this->values;
    }
    }

    View Slide

  71. function getCommentWordsForBlog($blog) {
    return Many::create($blog->categories)->then(function($category) {
    return Many::create($category->posts->then(function($post) {
    return Many::create($post->comments)->then(function($comment) {
    return explode(" ", $comment);
    });
    });
    })->value();
    }

    View Slide

  72. function getCommentWordsForBlog($blog) {
    return
    Many::create($blog->categories)
    ->then(function($category) {
    return $category->posts;
    })
    ->then(function($post) {
    return $post->comments;
    })
    ->then(function($comment) {
    return explode(" ", $comment);
    })
    ->value();
    }

    View Slide

  73. public function __call($method) {
    return $this->then(function($value) use ($method) {
    return $value->$method;
    });
    }
    public function __get($property) {
    return $this->$property();
    }

    View Slide

  74. function getCommentWordsForBlog($blog) {
    return
    Many::create($blog->categories)
    ->posts
    ->comments
    ->then(function($comment) {
    return explode(" ", $comment);
    })
    ->value();
    }

    View Slide

  75. WHY are we
    doing all of this?

    View Slide

  76. class Maybe {
    public function then(callable $func) {
    if ($this->value !== null) {
    return static::create($func($this->value));
    }
    return static::create(null);
    }
    ...
    }
    class Many {
    public function then(callable $func) {
    return static::create(
    flatMap($this->values, $func)
    );
    }
    ...
    }

    View Slide

  77. MAYBE and MANY
    are MONADS

    View Slide

  78. ABSTRACT data types

    View Slide

  79. OPERATIONS and RULES

    View Slide

  80. OPERATIONS
    ▸ ::then(callable $func)
    ▸ ::from(...$params)

    View Slide

  81. class Maybe {
    public static function from($value) {
    return static::create($value);
    }
    ...
    }
    class Many {
    public static function from($value) {
    return static::create([$value]);
    }
    ...
    }

    View Slide

  82. RULES
    ▸ ::then(callable $func)
    calls a function zero or more times, at some point
    ▸ ::then(callable $func)
    returns an instance of the same monad
    ▸ ::from(callable $func)
    doesn't mess with the value

    View Slide

  83. COMMON interface

    View Slide

  84. we can use THEN to connect
    MULTIPLE OPERATIONS
    together

    View Slide

  85. MAYBE →
    only do the next thing if
    value is not null

    View Slide

  86. MANY →
    do the next thing
    for each value

    View Slide

  87. (again) we can define more
    operations based on the
    known operations

    View Slide

  88. trait HasWithinMethod {
    public function within(callable $func) {
    return $this->then(function($value) use ($func) {
    return static::from($func($value));
    });
    }
    }

    View Slide

  89. class Maybe {
    use HasWithinMethod;
    ...
    }
    class Many {
    use HasWithinMethod;
    ...
    }

    View Slide

  90. function upperCaseNameWithin($value) {
    return $value->within(function($value) {
    return strtoupper($value["name"]);
    });
    }

    View Slide

  91. $maybe = Maybe::create(["name" => "chris"]);
    upperCaseNameWithin($maybe); // "CHRIS"
    $many = Many::create([["name" => "chris"], ["name" => "cal"]]);
    upperCaseNameWithin($many); // ["CHRIS", "CAL"]

    View Slide

  92. WHERE is this already
    happening?

    View Slide

  93. View Slide

  94. View Slide

  95. View Slide

  96. $(".cart").filter(selector).on("click", handleClick);

    View Slide

  97. View Slide

  98. DB::table("users")
    ->where("name", "=", "John")
    ->orWhere(function($query) {
    $query
    ->where("votes", ">", 100)
    ->where("title", "!=", "Admin");
    })

    View Slide

  99. monads are particularly
    good at ABSTRACTING
    REPEATED OPERATIONS

    View Slide

  100. In MAYBE the repeated operation being
    abstracted is NULL CHECKING

    View Slide

  101. In MANY the repeated operation being
    abstracted is FLAT MAP

    View Slide

  102. In PROMISES the repeated operation
    being abstracted is CALLING FUNCTIONS
    IN RESPONSE TO EVENTUAL VALUES

    View Slide

  103. In JQUERY the repeated operations being
    abstracted are ELEMENT SELECTION and
    MODIFICATION

    View Slide

  104. In ELOQUENT the repeated operations
    being abstracted are STATEFUL QUERY
    MODIFICATIONS

    View Slide

  105. When you see REPEATED
    OPERATIONS, consider how
    you can turn them into a
    MONAD

    View Slide

  106. ...and then GO TELL OTHERS
    about monads

    View Slide

  107. thank you
    HTTPS://JOIND.IN/17065
    HTTPS://TWITTER.COM/ASSERTCHRIS

    View Slide