Created
March 10, 2018 15:49
-
-
Save pwm/be9d403b59aa1ef0457fb020c55ca02f to your computer and use it in GitHub Desktop.
Some Maybe stuff
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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