gosuperscript / monads
Installs: 517 530
Dependents: 0
Suggesters: 0
Security: 0
Stars: 4
Watchers: 1
Forks: 1
Open Issues: 0
pkg:composer/gosuperscript/monads
Requires
- php: ^8.3
Requires (Dev)
- laravel/pint: ^1.17
- pestphp/pest: ^2.34
- phpstan/phpstan: ^2.0
This package is auto-updated.
Last update: 2025-11-11 07:24:36 UTC
README
A collection of useful monads for PHP 8.3+. Inspired by Rust's powerful type system and functional programming patterns.
Features
- ๐ฆ Rust-inspired API - Familiar methods for those coming from Rust
- ๐ Type-safe - Full PHPStan level 9 support with generics
- ๐งช Well-tested - Comprehensive test suite
- ๐ฆ Zero dependencies - Lightweight and focused
- ๐ฏ Three core monads:
Option<T>- Represent optional values without nullResult<T, E>- Handle errors without exceptionsLazy<T>- Defer computation until needed
Installation
You can install the package via Composer:
composer require gosuperscript/monads
Requirements
- PHP 8.3 or higher
Usage
Option Monad
The Option type represents an optional value: every Option is either Some and contains a value, or None, and does not. This is a safer alternative to using null.
use function Superscript\Monads\Option\{Some, None}; // Create an Option $some = Some(42); $none = None(); // Check if value exists $some->isSome(); // true $none->isNone(); // true // Transform the value $doubled = Some(21)->map(fn($x) => $x * 2); // Some(42) $empty = None()->map(fn($x) => $x * 2); // None // Provide default values Some(42)->unwrapOr(0); // 42 None()->unwrapOr(0); // 0 // Chain operations Some(10) ->filter(fn($x) => $x > 5) ->map(fn($x) => $x * 2) ->unwrapOr(0); // 20 // Convert to Result Some(42)->okOr("error"); // Ok(42) None()->okOr("error"); // Err("error")
Key Option Methods
isSome()/isNone()- Check if the option contains a valueisSomeAnd(callable $predicate)- Check if Some and matches predicatemap(callable $f)- Transform the contained valuefilter(callable $f)- Filter based on a predicateand(Option $other)/or(Option $other)- Combine optionsandThen(callable $f)- Chain operations (flatMap)unwrap()- Get the value (throws if None)unwrapOr($default)- Get the value or a defaultunwrapOrElse(callable $f)- Get the value or compute a defaultexpect(string|Throwable $message)- Unwrap with custom error message
Result Monad
Result<T, E> is the type used for returning and propagating errors. It is either Ok(T), representing success and containing a value, or Err(E), representing error and containing an error value.
use function Superscript\Monads\Result\{Ok, Err, attempt}; // Create Results $ok = Ok(42); $err = Err("something went wrong"); // Check the result $ok->isOk(); // true $err->isErr(); // true // Transform success values $doubled = Ok(21)->map(fn($x) => $x * 2); // Ok(42) $stillErr = Err("error")->map(fn($x) => $x * 2); // Err("error") // Transform error values $recovered = Err("error")->mapErr(fn($e) => "recovered"); // Err("recovered") // Handle both cases $result = Ok(10)->match( err: fn($e) => "Error: $e", ok: fn($x) => "Success: $x" ); // "Success: 10" // Chain operations Ok(10) ->map(fn($x) => $x * 2) ->andThen(fn($x) => $x > 15 ? Ok($x) : Err("too small")) ->unwrapOr(0); // 20 // Convert to Option Ok(42)->ok(); // Some(42) Err("e")->ok(); // None() // Safely execute code that might throw $result = attempt(fn() => json_decode($json, flags: JSON_THROW_ON_ERROR)); // Returns: Result<mixed, Throwable>
Key Result Methods
isOk()/isErr()- Check if the result is success or errormap(callable $f)- Transform the success valuemapErr(callable $f)- Transform the error valuemapOr($default, callable $f)- Transform or provide defaultmapOrElse(callable $default, callable $f)- Transform or compute defaultmatch(callable $err, callable $ok)- Handle both casesand(Result $other)/or(Result $other)- Combine resultsandThen(callable $f)- Chain operations (flatMap)unwrap()- Get the success value (throws if Err)unwrapErr()- Get the error value (throws if Ok)unwrapOr($default)- Get the value or a defaultunwrapOrElse(callable $f)- Get the value or compute a defaultexpect(string|Throwable $message)- Unwrap with custom error message
Lazy Monad
The Lazy type allows you to defer the execution of a computation until its result is actually needed.
use Superscript\Monads\Lazy\Lazy; // Create a lazy computation $lazy = Lazy::of(fn() => expensiveComputation()); // The computation hasn't run yet... // Evaluate when needed (memoized) $result = $lazy->evaluate(); // Runs the computation $cached = $lazy->evaluate(); // Returns cached result // Practical example: lazy database query $users = Lazy::of(fn() => DB::query("SELECT * FROM users")); if ($needUsers) { $data = $users->evaluate(); // Query runs only if needed }
Collection Operations
Both Option and Result support collecting arrays of values:
use function Superscript\Monads\Option\{Some, None}; use function Superscript\Monads\Result\{Ok, Err}; // Collect Options - returns first None or Some(array) Option::collect([Some(1), Some(2), Some(3)]); // Some([1, 2, 3]) Option::collect([Some(1), None(), Some(3)]); // None() // Collect Results - returns first Err or Ok(array) Result::collect([Ok(1), Ok(2), Ok(3)]); // Ok([1, 2, 3]) Result::collect([Ok(1), Err("e"), Ok(3)]); // Err("e")
Practical Examples
Safe Array Access
use Superscript\Monads\Option\Option; function getUser(int $id): Option { $user = DB::find('users', $id); return Option::from($user); // Returns None if null } $username = getUser(123) ->map(fn($user) => $user->name) ->unwrapOr('Guest');
Error Handling Without Exceptions
use function Superscript\Monads\Result\{Ok, Err, attempt}; function divide(int $a, int $b): Result { return $b === 0 ? Err("Division by zero") : Ok($a / $b); } $result = divide(10, 2) ->map(fn($x) => $x * 2) ->unwrapOr(0); // 10 $error = divide(10, 0) ->map(fn($x) => $x * 2) ->unwrapOr(0); // 0
Pipeline Processing
use function Superscript\Monads\Result\{Ok, Err}; function processData(array $data): Result { return Ok($data) ->andThen(fn($d) => validateData($d)) ->andThen(fn($d) => transformData($d)) ->andThen(fn($d) => saveData($d)); } $result = processData($input)->match( err: fn($e) => response()->json(['error' => $e], 400), ok: fn($d) => response()->json(['data' => $d], 200) );
Testing
The package uses Pest for testing:
# Run tests vendor/bin/pest # Run type checking vendor/bin/phpstan # Run code style fixer vendor/bin/pint
PHPStan Integration
This library provides full PHPStan support with generic types. The testing utilities include:
use Superscript\Monads\Result\Testing\ComparesResults; use Superscript\Monads\Option\Testing\ComparesOptions; class MyTest extends TestCase { use ComparesResults; use ComparesOptions; public function test_example() { // Custom assertions $this->assertOk(Ok(42)); $this->assertErr(Err("error")); $this->assertSome(Some(42)); $this->assertNone(None()); // PHPUnit constraints $this->assertThat(Ok(42), $this->isOk()); $this->assertThat(Err("e"), $this->isErr()); } }
Why Monads?
Monads help you write more predictable and maintainable code by:
- Making errors explicit - No hidden nulls or uncaught exceptions
- Enabling composition - Chain operations cleanly with
mapandandThen - Improving type safety - Let PHPStan catch errors at analysis time
- Reducing boilerplate - Less null checking and try-catch blocks
Inspiration
This library is heavily inspired by Rust's Option and Result types, bringing similar patterns to PHP.
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
The MIT License (MIT). Please see License File for more information.