PHPackages                             gosuperscript/monads - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. gosuperscript/monads

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

gosuperscript/monads
====================

v1.1.0(4mo ago)4601.2k↓21.1%1[1 PRs](https://github.com/gosuperscript/monads-php/pulls)6MITPHPPHP ^8.3CI passing

Since Sep 23Pushed 3mo ago1 watchersCompare

[ Source](https://github.com/gosuperscript/monads-php)[ Packagist](https://packagist.org/packages/gosuperscript/monads)[ RSS](/packages/gosuperscript-monads/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (2)Dependencies (3)Versions (7)Used By (6)

Monads for PHP
==============

[](#monads-for-php)

[![Latest Version on Packagist](https://camo.githubusercontent.com/1b1c1c4e6727b4d380522118bb039f52a5481bed576a964a4eb5e538d8efdda6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f676f73757065727363726970742f6d6f6e6164732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/gosuperscript/monads)[![Tests](https://camo.githubusercontent.com/7794fb8bd9b8c04d1aeb934a67ee5b91f50330ed41c76f08ca7fbe9622b4d7c1/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f676f73757065727363726970742f6d6f6e6164732d7068702f63692e79616d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/gosuperscript/monads-php/actions)[![License](https://camo.githubusercontent.com/26a2adcf2e3a52e73a0a94ab97ea4e8e85ae29f08682c1784dc91ed2eccaf28b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f676f73757065727363726970742f6d6f6e6164733f7374796c653d666c61742d737175617265)](https://packagist.org/packages/gosuperscript/monads)

A collection of useful monads for PHP 8.3+. Inspired by Rust's powerful type system and functional programming patterns.

Features
--------

[](#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
- 🎯 **Four core monads**:
    - **`Option`** - Represent optional values without null
    - **`Result`** - Handle errors without exceptions
    - **`Lazy`** - Defer computation until needed
    - **`Writer`** - Carry a value alongside an accumulated log

Installation
------------

[](#installation)

You can install the package via Composer:

```
composer require gosuperscript/monads
```

Requirements
------------

[](#requirements)

- PHP 8.3 or higher

Usage
-----

[](#usage)

### Option Monad

[](#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

[](#key-option-methods)

- `isSome()` / `isNone()` - Check if the option contains a value
- `isSomeAnd(callable $predicate)` - Check if Some and matches predicate
- `map(callable $f)` - Transform the contained value
- `filter(callable $f)` - Filter based on a predicate
- `and(Option $other)` / `or(Option $other)` - Combine options
- `andThen(callable $f)` - Chain operations (flatMap)
- `unwrap()` - Get the value (throws if None)
- `unwrapOr($default)` - Get the value or a default
- `unwrapOrElse(callable $f)` - Get the value or compute a default
- `expect(string|Throwable $message)` - Unwrap with custom error message

### Result Monad

[](#result-monad)

`Result` 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
```

#### Key Result Methods

[](#key-result-methods)

- `isOk()` / `isErr()` - Check if the result is success or error
- `map(callable $f)` - Transform the success value
- `mapErr(callable $f)` - Transform the error value
- `mapOr($default, callable $f)` - Transform or provide default
- `mapOrElse(callable $default, callable $f)` - Transform or compute default
- `match(callable $err, callable $ok)` - Handle both cases
- `and(Result $other)` / `or(Result $other)` - Combine results
- `andThen(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 default
- `unwrapOrElse(callable $f)` - Get the value or compute a default
- `expect(string|Throwable $message)` - Unwrap with custom error message

### Lazy Monad

[](#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
}
```

### Writer Monad

[](#writer-monad)

The `Writer` monad lets you carry a value alongside an accumulated log through a chain of computations. This is useful for logging, auditing, or building up auxiliary data without side effects.

```
use Superscript\Monads\Writer\Writer;
use function Superscript\Monads\Writer\Writer;

// Create a Writer with an array-based log
$writer = Writer(42, ['initial value']);

// Access value and log
$writer->value(); // 42
$writer->log();   // ['initial value']
$writer->run();   // [42, ['initial value']]

// Transform the value (log unchanged)
Writer(21)->map(fn($x) => $x * 2); // Writer(42, [])

// Chain computations that produce their own logs
Writer(10, ['start'])
    ->andThen(fn($x) => Writer($x * 2, ['doubled']))
    ->andThen(fn($x) => Writer($x + 1, ['incremented']));
// value: 21, log: ['start', 'doubled', 'incremented']

// Append to the log without changing the value
Writer(42)->tell(['something happened']);
// value: 42, log: ['something happened']

// Use a custom log type with Writer::of()
$writer = Writer::of('hello', '', fn(string $a, string $b): string => $a . $b)
    ->tell(' world')
    ->map(fn($v) => strtoupper($v));
// value: 'HELLO', log: ' world'
```

#### Key Writer Methods

[](#key-writer-methods)

- `value()` - Get the contained value
- `log()` - Get the accumulated log
- `run()` - Get both as a `[$value, $log]` tuple
- `map(callable $f)` - Transform the value, leaving the log unchanged
- `andThen(callable $f)` - Chain a computation that returns a Writer, combining logs (flatMap)
- `tell($entry)` - Append to the log without changing the value
- `mapLog(callable $f)` - Transform the log
- `inspect(callable $f)` - Execute a side effect with the value
- `listen(callable $f)` - Access both value and log to produce a new value
- `reset($log)` - Reset the log to a given value

### Collection Operations

[](#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

[](#practical-examples)

#### Safe Array Access

[](#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

[](#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
```

#### Computation with Logging

[](#computation-with-logging)

```
use function Superscript\Monads\Writer\Writer;

function addTax(float $price): Writer {
    $taxed = $price * 1.2;
    return Writer($taxed, [sprintf('Tax: %.2f -> %.2f', $price, $taxed)]);
}

function applyDiscount(float $price): Writer {
    $discounted = $price * 0.9;
    return Writer($discounted, [sprintf('Discount: %.2f -> %.2f', $price, $discounted)]);
}

$result = Writer(100.0, ['Starting price: 100.00'])
    ->andThen(fn($p) => addTax($p))
    ->andThen(fn($p) => applyDiscount($p));

$result->value(); // 108.0
$result->log();   // ['Starting price: 100.00', 'Tax: 100.00 -> 120.00', 'Discount: 120.00 -> 108.00']
```

#### Pipeline Processing

[](#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
-------

[](#testing)

The package uses [Pest](https://pestphp.com/) for testing:

```
# Run tests
vendor/bin/pest

# Run type checking
vendor/bin/phpstan

# Run code style fixer
vendor/bin/pint
```

PHPStan Integration
-------------------

[](#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?
-----------

[](#why-monads)

Monads help you write more predictable and maintainable code by:

1. **Making errors explicit** - No hidden nulls or uncaught exceptions
2. **Enabling composition** - Chain operations cleanly with `map` and `andThen`
3. **Improving type safety** - Let PHPStan catch errors at analysis time
4. **Reducing boilerplate** - Less null checking and try-catch blocks

Inspiration
-----------

[](#inspiration)

This library is heavily inspired by Rust's [`Option`](https://doc.rust-lang.org/std/option/) and [`Result`](https://doc.rust-lang.org/std/result/) types, bringing similar patterns to PHP.

Changelog
---------

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Contributions are welcome! Please feel free to submit a Pull Request.

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE) for more information.

###  Health Score

52

—

FairBetter than 96% of packages

Maintenance79

Regular maintenance activity

Popularity41

Moderate usage in the ecosystem

Community21

Small or concentrated contributor base

Maturity55

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 53.6% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~106 days

Total

2

Last Release

131d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/de7b0560fc6f16a90b64b73f125ab92331171f08d22a1569972ead0341eb9c01?d=identicon)[erikgaal](/maintainers/erikgaal)

---

Top Contributors

[![erikgaal](https://avatars.githubusercontent.com/u/1234268?v=4)](https://github.com/erikgaal "erikgaal (15 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (5 commits)")[![joelbutcher](https://avatars.githubusercontent.com/u/7163152?v=4)](https://github.com/joelbutcher "joelbutcher (4 commits)")[![robertvansteen](https://avatars.githubusercontent.com/u/14931924?v=4)](https://github.com/robertvansteen "robertvansteen (3 commits)")[![eithed](https://avatars.githubusercontent.com/u/795678?v=4)](https://github.com/eithed "eithed (1 commits)")

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/gosuperscript-monads/health.svg)

```
[![Health](https://phpackages.com/badges/gosuperscript-monads/health.svg)](https://phpackages.com/packages/gosuperscript-monads)
```

###  Alternatives

[khill/fontawesomephp

PHP wrapper library for the fantastic Font Awesome icon set.

4676.3k4](/packages/khill-fontawesomephp)[wireui/breadcrumbs

Breadcrumbs component for Laravel, Livewire, TallStack

3817.0k](/packages/wireui-breadcrumbs)[smithfield-studio/acf-svg-icon-picker

Add ACF field for selecting SVG icons.

3710.8k](/packages/smithfield-studio-acf-svg-icon-picker)[geowrgetudor/laravel-balance

A credit system / balance for Laravel.

217.8k](/packages/geowrgetudor-laravel-balance)[imarc/opus

Multi-framework asset and module packaging for composer

1112.1k3](/packages/imarc-opus)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
