Skip to content

Instantly share code, notes, and snippets.

@pwm
Created March 10, 2018 15:49
Show Gist options
  • Save pwm/be9d403b59aa1ef0457fb020c55ca02f to your computer and use it in GitHub Desktop.
Save pwm/be9d403b59aa1ef0457fb020c55ca02f to your computer and use it in GitHub Desktop.
Some Maybe stuff
<?php
declare(strict_types=1);
// ((a, ...) -> z) -> a -> ... -> z
function curry(callable $f, ...$args) {
return function (...$partialArgs) use ($f, $args) {
return (function ($args) use ($f) {
return \count($args) < (new \ReflectionFunction($f))->getNumberOfRequiredParameters()
? curry($f, ...$args)
: $f(...$args);
})(\array_merge($args, $partialArgs));
};
}
interface Functor {
// Functor F => (a -> b) -> F b
public function map(callable $f)/*: Functor*/;
}
// Functor F => (a -> b) -> F a -> F b
function fmap(...$args) {
return curry(function(callable $f, Functor $F) {
return $F->map($f);
})(...$args);
}
interface Applicative extends Functor {
// Applicative F => a -> F a
public static function pure($x)/*: Applicative*/;
// Applicative F => F a -> F b
public function ap(Applicative $F)/*: Applicative*/;
}
// Applicative A => A (a -> b) -> A a -> A b
function ap(...$args) {
return curry(function(Applicative $F1, Applicative $F2) {
return $F1->ap($F2);
})(...$args);
}
// Applicative A => (a -> b -> c) -> A a -> A b -> A c
function liftA2(...$args) {
return curry(function (callable $f, Applicative $F1, Applicative $F2) {
return $F1->map($f)->ap($F2);
})(...$args);
}
interface Monad extends Applicative {
// Monad M => M (M a) -> M a
public function join()/*: Monad*/;
// Monad M => (a -> M b) -> M b
public function bind(callable $f)/*: Monad*/;
}
// Monad M => (a -> M b) -> M a -> M b
function bind(...$args) {
return curry(function (callable $f, Monad $M) {
return $M->map($f)->join();
})(...$args);
}
// ====
class Maybe implements Functor, Applicative, Monad
{
/** @var bool */
private $isJust;
/** @var mixed|null */
private $value;
public static function just($value): Maybe
{
return new self(true, $value);
}
public static function nothing(): Maybe
{
return new self(false);
}
public function match(callable $just, callable $nothing)
{
return $this->isJust
? $just($this->value)
: $nothing();
}
public function map(callable $f): Maybe
{
return $this->isJust
? self::just($f($this->value))
: self::nothing();
}
public static function pure($value): Maybe
{
return self::just($value);
}
public function ap(Applicative $F): Maybe
{
return $F->map($this->value);
}
public function join(): Maybe
{
return $this->isJust
? $this->value
: self::nothing();
}
public function bind(callable $f): Maybe
{
return $this->map($f)->join();
}
private function __construct(bool $isJust, $value = null)
{
$this->isJust = $isJust;
$this->value = $value;
}
}
// a -> Maybe a -> a
function fromMaybe(...$args) {
return curry(function ($default, Maybe $a) {
return $a->match(
function ($value) { return $value; },
function () use ($default) { return $default; }
);
})(...$args);
}
// b -> (a -> b) -> Maybe a -> b
function maybe(...$args) {
return curry(function($default, callable $f, Maybe $a) {
return $a->match(
function ($value) use ($f) { return $f($value); },
function () use ($default) { return $default; }
);
})(...$args);
}
// ====
// example 1
function odd(int $x): bool { return $x % 2 === 1; }
assert(true === maybe('nope', 'odd', Maybe::just(1)));
assert(false === maybe('nope', 'odd', Maybe::just(2)));
assert('nope' === maybe('nope', 'odd', Maybe::nothing()));
// example 2
function add(...$args) {
return curry(function (int $x, int $y): int {
return $x + $y;
})(...$args);
}
assert(3 === fromMaybe('nope', liftA2('add', Maybe::just(1), Maybe::just(2))));
assert('nope' === fromMaybe('nope', liftA2('add', Maybe::just(1), Maybe::nothing())));
// example 3
function divByX(int $x): Maybe/*int*/ {
return $x !== 0
? Maybe::just(10 / $x)
: Maybe::nothing();
}
assert(5 === fromMaybe('nope', bind('divByX', Maybe::just(2))));
assert('nope' === fromMaybe('nope', bind('divByX', Maybe::just(0))));
assert('nope' === fromMaybe('nope', bind('divByX', Maybe::nothing())));
// example 4
class X {
private $a;
public function __construct(Maybe/*int*/ $a)
{
$this->a = $a;
}
public function getA(): Maybe/*int*/
{
return $this->a;
}
}
assert(1 === fromMaybe('nope', (new X(Maybe::just(1)))->getA()));
assert('nope' === fromMaybe('nope', (new X(Maybe::nothing()))->getA()));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment