franzose / lemonad
Some monads implemented in PHP
dev-master
2019-02-01 01:35 UTC
Requires
- php: ^7.2
Requires (Dev)
- phpstan/phpstan: ^0.11.0
- phpunit/phpunit: ^7.5
- vimeo/psalm: ^3.0
This package is auto-updated.
Last update: 2024-10-29 05:40:36 UTC
README
It is a small repository containing implementations of some monads.
Optional
Optional
is useful when you don‘t exactly sure if you‘re dealing with an empty value or some meaningful one. It provides a clean and expressive API.
<?php declare(strict_types=1); use Lemonad\Optional; use function Lemonad\optional; $empty = Optional::empty(); $empty->isPresent(); // false $empty->isAbsent(); // true $optOf42 = Optional::of(42); $optOf42->isPresent(); // true $optOf42->isAbsent(); // false $null = optional(null); $null->equals(Optional::empty()); // true $null->isAbsent(); // true $null->isPresent(); // false $present = Optional::of(42)->filter(function (int $value) { return 42 === $value; }); $present->isPresent(); // true $present->isAbsent(); // false $absent = Optional::of(42)->filter(function (int $value) { return 43 === $value; }); $absent->isPresent(); // false $absent->isAbsent(); // true optional(42)->map(function (int $value) { return $value + 1; })->get(); // 43 Optional::of(42)->flatMap(function (int $value) { return Optional::of($value + 1); })->get(); // 43 Optional::of(42)->ifPresent(function (int $value) { echo $value; }); // should output "42" Optional::ofNullable(null)->ifPresentOrElse(function (int $value) { echo $value; }, function () { echo '999'; }); // should output "999" Optional::ofNullable(null)->or(function () { return Optional::of(42); })->get(); // 42 Optional::ofNullable(null)->orElse(42); // 42 Optional::ofNullable(null)->orElseGet(function () { return 42; }); // 42 Optional::ofNullable(null)->orElseThrow(function () { return new \InvalidArgumentException('No! No! No!'); }); // it will throw \InvalidArgumentException exception
Maybe
Here are some examples of the Maybe monad:
<?php declare(strict_types=1); use Lemonad\Maybe; use function Lemonad\maybe; $unknown = Maybe::unknown(); // unknown forever $unknown->isKnown(); // false $known = Maybe::definitely(42); $known->isKnown(); // true Maybe::of(null)->isKnown(); // false Maybe::of(42)->isKnown(); // true Maybe::of(42)->or(999); // 42 Maybe::of(42)->orElse(Maybe::definitely(999)); // same instance with value of 42 Maybe::of(42)->to(function (int $value) { return $value + 1; }); // Maybe with value of 43 maybe(42)->query(function (int $value) { return 42 === $value; }); // Maybe with value of boolean true Maybe::of(42)->query(function (int $value) { return 43 === $value; }); // Maybe with value of boolean false Maybe::of(null)->or(42); // 42 Maybe::of(null)->orElse(Maybe::definitely(42)); // Maybe with value of 42 Maybe::of(null)->to(function () {}); // always a new instance of 'unknown' Maybe Maybe::of(null)->query(function () {}); // always a new instance of 'unknown' Maybe Maybe::of(null)->equals(Maybe::unknown()); // equals is always false echo Maybe::definitely('Brian')->to(function (string $name) { return $name . ' Adams'; }); // will output "Brian Adams"
Try
A Try represents a computation that may either throw an exception or return a value. As try
is a reserved keyword in PHP and class names are case insensitive, I could not use Try
for the class name, so I named it LetsTry
.
<?php declare(strict_types=1); use Lemonad\LetsTry; use function Lemonad\lets_try; use function Lemonad\noop; use RuntimeException; LetsTry::perform(function () { return 42; })->getOrElse(noop()); // 42 lets_try(function () { throw new RuntimeException('Argh!'); })->getOrElse(function () { return 42; }); // 42 LetsTry::successful(42)->getOrElse(noop()); // 42 LetsTry::successful(null); // will throw Lemonad\Exception\NullValueException LetsTry::failure(new RuntimeException('Argh!')) ->getOrElse(function () { return 42; }); // 42 // Successful, do mapping lets_try(function () { return 42; })->map(function (int $value) { return $value + 1; })->getOrElse(noop()); // 43 lets_try(function () { throw new RuntimeException('Argh!'); })->map(function () { // this callback will not be called, // new instance of `Failure` will be returned instead }); // Successful, do mapping lets_try(function () { return 42; })->flatMap(function (int $value) { return lets_try(function () use ($value) { return $value + 1; }); })->getOrElse(noop()); // 43 lets_try(function () { throw new RuntimeException('Argh!'); })->flatMap(function () { // again, this callback will not be called, // new instance of `Failure` will be returned instead }); // There's nothing to recover, // so the callback will not be called lets_try(function () { return 42; })->recover(function () { return 43; })->getOrElse(noop()); // 42 // Again, there's nothing to recover, // so the callback will not be called lets_try(function () { return 42; })->recoverWith(function () { return lets_try(function () { return 43; }); })->getOrElse(noop()); // 42 // Here, exception was thrown, // so we need to recover from that lets_try(function () { throw new RuntimeException('Argh!'); })->recover(function (RuntimeException $exception) { return $exception->getMessage(); }); // Argh! // Again, exception was thrown, // so we need to recover from that lets_try(function () { throw new RuntimeException('Argh!'); })->recoverWith(function (RuntimeException $exception) { return lets_try(function () use ($exception) { return $exception->getMessage(); }); }); // Argh! lets_try(function () { return 42; })->orElse(function () { return lets_try(function () { return 43; }); })->getOrElse(noop()); // 42 as the Try is `Success` lets_try(function () { throw new RuntimeException('Argh!'); })->orElse(function () { return lets_try(function () { return 43; }); })->getOrElse(noop()); // 43 as the Try was `Failure` // Will perform another Try lets_try(function () { return 42; })->filterOrElse( function (int $value) { return 42 === $value; }, function () { return new RuntimeException('Argh!'); } ); // Will throw provided RuntimeException, // as the predicate is unsatisfied lets_try(function () { return 42; })->filterOrElse( function (int $value) { return 99 === $value; }, function () { return new RuntimeException('Argh!'); } ); // Will just return another `Failure` Try lets_try(function () { throw new RuntimeException('Argh!'); })->filterOrElse( function (int $value) { return 99 === $value; }, function () { return new RuntimeException('Argh!'); } ); lets_try(function () { return 42; })->fold(noop(), function (int $value) { return $value + 1; }); // 43 lets_try(function () { throw new RuntimeException('Argh!'); })->fold( function (RuntimeException $exception) { return $exception->getMessage(); }, function (int $value) { return $value + 1; } ); // Argh! lets_try(function () { return 42; })->toOptional(); // Optional which contains value of 42 lets_try(function () { throw new RuntimeException('Argh!'); })->toOptional(); // An empty Optional lets_try(function () { return 42; })->forEach(function (int $value) { // do something }); lets_try(function () { throw new RuntimeException('Argh!'); })->forEach(function () { // for `Failure`s it is a noop }); lets_try(function () { return 42; })->isSuccess(); // true lets_try(function () { throw new RuntimeException('Argh!'); })->isFailure(); // true