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.3.0(1mo ago)6624.7k↓76.1%1[1 PRs](https://github.com/gosuperscript/monads-php/pulls)6MITPHPPHP ^8.3CI passing

Since Sep 23Pushed 1mo ago1 watchersCompare

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

READMEChangelog (4)Dependencies (9)Versions (10)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

57

—

FairBetter than 98% of packages

Maintenance94

Actively maintained with recent releases

Popularity42

Moderate usage in the ecosystem

Community23

Small or concentrated contributor base

Maturity57

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 51.4% 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 ~83 days

Total

4

Last Release

33d 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 (18 commits)")[![joelbutcher](https://avatars.githubusercontent.com/u/7163152?v=4)](https://github.com/joelbutcher "joelbutcher (8 commits)")[![Copilot](https://avatars.githubusercontent.com/in/1143301?v=4)](https://github.com/Copilot "Copilot (5 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

[php-tuf/php-tuf

PHP implementation of The Update Framework (TUF)

4510.2k1](/packages/php-tuf-php-tuf)

PHPackages © 2026

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