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.

061e3bae4ce4234a2194d20a382e5d19?s=128

Christopher Pitt

May 27, 2016
Tweet

Transcript

  1. None
  2. MONADS in PHP

  3. Thanks to PHP[TEK]

  4. Thanks to SILVERSTRIPE

  5. My GOAL

  6. What is a STACK?

  7. OPERATIONS and RULES

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

    mixed ▸ Stack::isEmpty(): bool
  9. RULES ▸ $stack->push($value)->top() == $value ▸ $stack->push($value)->pop() == $stack ▸

    $stack->push($value)->isEmpty() == false ▸ (new Stack)->isEmpty() == true
  10. we can implement these operations however we like

  11. class ArrayStack { private $values; public function __construct(array $values =

    []) { $this->values = $values; }
  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; }
  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; } }
  14. class LinkedListStack { private $head; private $tail; public function __construct($head

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

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

    { return $this->tail === null; } }
  17. we can define more operations based on the known operations

  18. trait HasSizeMethod { public function size() { if ($this->isEmpty()) {

    return 0; } return $this->pop()->size() + 1; } abstract public function isEmpty(); abstract public function pop(); }
  19. class ArrayStack { use HasSizeMethod; ... } class LinkedListStack {

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

    LinkedListStack(); $stack2->push("one")->push("two")->size(); // 2
  21. STACK is a SPECIFICATION

  22. COMMON interface

  23. SHARED functionality

  24. what is a COLLECTION?

  25. OPERATIONS and RULES

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

  27. RULES ▸ Collection::each(callable $func) calls a function zero or more

    times, in immediate sequence
  28. (again) we can implement these operations however we like

  29. class RangeCollection { private $range; public function __constuct(array $range =

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

    $func($value); } } }
  31. class HourIntervalCollection { private $start; private $end; public function __construct($start,

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

    $next = $this->start + interval; while ($next <= $this->end) { $func($next); $next += interval; } } }
  33. (again) we can define more operations based on the known

    operations
  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); }
  35. class RangeCollection { use HasSelectMethod; ... } class HourIntervalCollection {

    use HasSelectMethod; ... }
  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]
  37. COLLECTION is a SPECIFICATION

  38. COMMON interface

  39. SHARED functionality

  40. what are STACK and COLLECTION?

  41. ABSTRACT data types

  42. let's refactor some code!

  43. handling NULL

  44. class Store { public $manager; } class Manager { public

    $address; } class Address { public $city; }
  45. $address = new Address(); $address->city = "St. Louis"; $manager =

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

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

    // error
  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 } } } }
  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 } }
  50. trait HasTryMethod { public function try($property) { if ($this->$property !==

    null) { return $this->$property; } return null; } }
  51. class Store { use HasTryMethod; public $manager; } class Manager

    { use HasTryMethod; public $address; } class Address { use HasTryMethod; public $city; }
  52. function getManagerCityForStore($store) { if ($store !== null) { $manager =

    $store->try("manager"); $address = $manager->try("address"); $city = $address->try("city"); return $city; } }
  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) }
  54. public function try($property) { if ($this->value !== null) { return

    $this->value->$property; } return null; } public function value() { return $this->value; } }
  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; }
  56. public function try($property) { if ($this->value !== null) { return

    static::create($this->value->$property); } return static::create(null); }
  57. function getManagerCityForStore($store) { $store = Maybe::create($store); $manager = $store->try("manager"); $address

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

  59. public function try(callable $func) { if ($this->value !== null) {

    return static::create($func($this->value)); } return static::create(null); }
  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(); }
  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(); }
  62. function getManagerCityForStore($store) { return Maybe::create($store) ->manager ->address ->city ->value(); }

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

  64. handling MANY

  65. class Blog { public $categories; } class Category { public

    $posts; } class Post { public $comments; }
  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];
  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; } } }
  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); }); }); }); }
  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); }
  70. public function then(callable $func) { return static::create( flatMap($this->values, $func) );

    } public function value() { return $this->values; } }
  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(); }
  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(); }
  73. public function __call($method) { return $this->then(function($value) use ($method) { return

    $value->$method; }); } public function __get($property) { return $this->$property(); }
  74. function getCommentWordsForBlog($blog) { return Many::create($blog->categories) ->posts ->comments ->then(function($comment) { return

    explode(" ", $comment); }) ->value(); }
  75. WHY are we doing all of this?

  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) ); } ... }
  77. MAYBE and MANY are MONADS

  78. ABSTRACT data types

  79. OPERATIONS and RULES

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

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

    } ... } class Many { public static function from($value) { return static::create([$value]); } ... }
  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
  83. COMMON interface

  84. we can use THEN to connect MULTIPLE OPERATIONS together

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

    not null
  86. MANY → do the next thing for each value

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

    operations
  88. trait HasWithinMethod { public function within(callable $func) { return $this->then(function($value)

    use ($func) { return static::from($func($value)); }); } }
  89. class Maybe { use HasWithinMethod; ... } class Many {

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

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

    Many::create([["name" => "chris"], ["name" => "cal"]]); upperCaseNameWithin($many); // ["CHRIS", "CAL"]
  92. WHERE is this already happening?

  93. None
  94. None
  95. None
  96. $(".cart").filter(selector).on("click", handleClick);

  97. None
  98. DB::table("users") ->where("name", "=", "John") ->orWhere(function($query) { $query ->where("votes", ">", 100)

    ->where("title", "!=", "Admin"); })
  99. monads are particularly good at ABSTRACTING REPEATED OPERATIONS

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

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

  102. In PROMISES the repeated operation being abstracted is CALLING FUNCTIONS

    IN RESPONSE TO EVENTUAL VALUES
  103. In JQUERY the repeated operations being abstracted are ELEMENT SELECTION

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

    MODIFICATIONS
  105. When you see REPEATED OPERATIONS, consider how you can turn

    them into a MONAD
  106. ...and then GO TELL OTHERS about monads

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