Monads in PHP

Based on the work
of Tom Stuart

Monads are an
ordinary data type...

...you are already using them.

What is a Stack?

  1. A data type

  1. First-in, last-out

Operations and rules

$stack = new Stack(); // add 1 item to the top $stack->push($value): Stack // get the top value $stack->top(): $value // get rid of the top value $stack->pop(): Stack // check if the stack is empty $stack->isEmpty(): bool
$stack->push($value)->top() === $value $stack->push($value)->pop() === $stack $stack->push($value)->isEmpty() === false // PHP 8.4! new Stack()->isEmpty() === true

We can implement however we like...

class ArrayStack { public function __construct( private array $items = [], ) {} public function push(mixed $value): self { return new self([...$this->items, $value]); } public function top(): mixed { if (!$this->isEmpty()) { return $this->items[count($this->items) - 1]; } throw new Exception('Stack is empty'); } public function pop(): self { return new self( array_slice($this->items, 0, count($this->items) - 1) ); } public function isEmpty(): bool { return count($this->items) < 1; } }
class LinkedListStack { public function __construct( private mixed $head = null, private null|self $tail = null, ) {} public function push(mixed $value): self { return new self($value, $this); } public function top(): mixed { return $head; } public function pop() { return $this->tail; } public function isEmpty() { return is_null($this->tail); } }

We can define
new operations...

trait Size { public function size() { if (!$this->isEmpty()) { return 0; } return $this->pop()->size() + 1; } abstract public function isEmpty(): bool; abstract public function pop(): self; }

Stack is a specification

Common interface

Derive new functionality

What is a Collection?

Operations and rules

$collection = new Collection(); // run something on each item $collection->each(Closure $each): Collection
class ArrayCollection { public function __construct( private array $items = [], ) {} public function each(Closure $each) { foreach ($this->items as $item) { $each($item); } return $this; } } $collection = new ArrayCollection([1, 2, 3]); $collection->each(fn($item) => var_dump($item)); // int(1) // int(2) // int(3)
class RangeCollection { public function __construct( private string|int|float $start, private string|int|float $end, private int|float $step, ) {} public function each(Closure $each): self { $items = range($this->start, $this->end, $this->step); foreach ($items as $item) { $each($item); } return $this; } } $collection = new RangeCollection('a', 'c'); $collection->each(fn($item) => var_dump($item)); // string(1) "a" // string(1) "b" // string(1) "c"

We can (again) define
more operations...

trait Filter { public function filter(Closure $filter): array { $values = []; $this->each(function($item) use ($values, $filter) { if ($filter($item)) { $values[] = $item; } }); return $values; } abstract public function each(Closure $each): self; }

Collection is a specification

Common interface

Derive new functionality

What are Stack and Collection?

Abstract data types

A little code to
prove the point...

Maybe, Many, Fluent → Monads

Monads are an abstract data type

Operations and rules

$maybe = new Maybe(T); $maybe->then(string $property): Maybe; $maybe->value(): T; $many = new Many(T); $many->then(Closure $mapper): Many; $many->value(): T; $fluent = new Fluent(T); $fluent->then(string $property, mixed $value): Fluent; $fluent->value(): T;

Where are we
already using this?

fetch('https://example.com/api') .then(response => response.json()) .then(json => console.log(json.result))
$('blockquote') .css('color', '#333') .filter('img') .css('margin-top', '1rem')
Books::query() ->with('author', 'images') ->where('author.name', '=', 'Christopher Pitt') ->orderBy('published_at', 'desc') ->limit(5) ->get();

Monads are good
for repeated operations

Maybe →
repeated null-checking

Many →
repeated flatMap

Fluent →
repeated configuration

Promises →
repeated operations
on future values

jQuery →
repeated filtering and
mutating of elements

Query Builder →
repeated SQL building

When you see yourself repeating operations...

...think of how it could be a Monad.

And, tell your friends!

Back to assertchris.dev →