Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <?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()));
Add Comment
Please, Sign In to add comment