PHPackages                             penguinpark/monad - 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. penguinpark/monad

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

penguinpark/monad
=================

A collection of PHP classes that implement monad-based operations

0.1.1(8mo ago)11Apache-2.0PHPPHP &gt;=8.4CI passing

Since Aug 26Pushed 8mo agoCompare

[ Source](https://github.com/RichardVasquez/penguinpark-monad)[ Packagist](https://packagist.org/packages/penguinpark/monad)[ RSS](/packages/penguinpark-monad/feed)WikiDiscussions canon Synced 1mo ago

READMEChangelog (1)Dependencies (2)Versions (2)Used By (0)

PenguinPark/Monad
=================

[](#penguinparkmonad)

> A small pile of PHP monads (and friends) that makes functional-style code not terrible.

Why this exists (a tiny tale of regret)
---------------------------------------

[](#why-this-exists-a-tiny-tale-of-regret)

I made a mistake.

I decided to make a parser, and I’d gotten very used to what the **Sprache** library does for .NET. Underneath it is a bunch of LINQ magic - which PHP does not have. So: a couple of years of poking at **ANTLR**, reading about **packrat parsers**, trying to remember the difference between **LL** and **LR** (no need to explain it to me), and getting **really, really peeved**.

Then I ran across **OCaml**, and all these friendly tutorials showing how easy and *pleasant* combinator parsers are. Great! Still not PHP. *sigh*

The “obvious solution” was to write a parser in C and bind it into PHP. No. Wait. Bad idea.

So I looked at everything I’d picked up over the years… **we hates it, we does; it burns, precious!**

The need didn’t go away: I still want a combinator parser. My first drafts were drowning in edge cases and brittle failure paths.

**Start over.** Give the parser a foundation. This library is that foundation. *Tada!*

It’s licensed **Apache-2.0** - please do whatever you want with it. Send PRs; I know I’m not covering everything (yet) in functions or tests. I’m calling this “good enough for now.” Enjoy!

---

Features
--------

[](#features)

- The usual suspects:
    - `Maybe` – optional values without `null` sadness
    - `Either` – success (`Right`) or error (`Left`) with typed error channels
    - `IO` – wrap side effects (`delay`, `mapError`, `handleErrorWith`, `bracket`)
    - `Reader` – thread read-only context (config, services) through pure code
    - `State` – state threading: `S -> (S, A)`
    - `Writer` – accumulate logs alongside values
    - `ListM` – list/sequence monad (cartesian/branching)
- Clear exceptions (0.1.0+): domain-specific error types instead of generic runtime errors
- Helpful docs under `docs/`
- PHPUnit + Infection setup (mutation testing)

> **Coming from other FP ecosystems?**
> See [`docs/aliases.md`](docs/aliases.md) for a Rosetta Stone of names (Haskell, Cats, Arrow, Fantasy Land, Elm, Rust, Swift, F#).

---

Install
-------

[](#install)

Requires **PHP 8.4+**

```
composer require penguinpark/monad
```

Autoloads under `PenguinPark\Monad\`.

---

Quick taste
-----------

[](#quick-taste)

### Maybe

[](#maybe)

```
use PenguinPark\Monad\Maybe\Maybe;

$answer = Maybe::of(41)
    ->map(fn (int $x) => $x + 1)
    ->getOrElse(0); // 42
```

### Either

[](#either)

```
use PenguinPark\Monad\Either\Either;
use PenguinPark\Monad\Either\Left;
use PenguinPark\Monad\Either\Right;

$parseInt = function(string $s): Either {
    return ctype_digit($s) ? new Right((int)$s) : new Left('bad int');
};

$result = $parseInt('123')
    ->map(fn (int $n) => $n + 1)                 // Right(124)
    ->getOrElse(0);                              // 124

$error  = $parseInt('nope')->getOrElse(0);       // 0
```

### IO

[](#io)

```
use PenguinPark\Monad\IO\IO;
use InvalidArgumentException;

// Lazy effect
$io = IO::delay(fn () => file_get_contents('/etc/hosts'))
    ->map('strlen')
    ->mapError(fn (\Throwable $e) => new InvalidArgumentException('read failed', previous: $e));

$len = $io->unsafeRun(); // int
```

Resource-safety:

```
use PenguinPark\Monad\IO\IO;

$readFirstLine = IO::bracket(
    acquire: fn () => fopen('/etc/hosts', 'r'),
    use:     fn ($fh)  => IO::delay(fn () => fgets($fh)),
    release: fn ($fh)  => IO::delay(fn () => fclose($fh))
);

$line = $readFirstLine->unsafeRun();
```

### Writer

[](#writer)

```
use PenguinPark\Monad\Writer\Writer;

[$log, $val] = Writer::tell('start')
    ->map(fn () => 'value')
    ->tell('done')
    ->run();        // $log = ['start','done'], $val = 'value'
```

---

Error model (custom exceptions)
-------------------------------

[](#error-model-custom-exceptions)

Common failure modes throw specific exceptions under `PenguinPark\Monad\Exception`:

- `UnwrapNothing`, `UnwrapLeft`
- `ContractViolation` (shape/type contracts)
- `InvalidBindReturnType`, `ApCallableExpected`, `BracketReturnTypeExpected`

This lets tests assert precise failures instead of string messages.

---

Validation (Applicative) - *optional, if enabled*
-------------------------------------------------

[](#validation-applicative---optional-if-enabled)

If you’ve pulled in the **0.1.1** changes:

- `Validation`, `Valid`, `Invalid` accumulate *all* errors via `ap`.
- Designed for data validation; there is **no `flatMap`** (applicative focus).

```
use PenguinPark\Monad\Validation\Validation;

$build = Validation::of(fn ($e) => fn ($a) => fn ($s) => compact('e','a','s'));

$res = $build
  ->ap(vEmail('bad@ex'))   // -> invalid('email: bad format')
  ->ap(vAge(15))           // -> invalid('age: must be ≥ 18')
  ->ap(vSalary(-10));      // -> invalid('salary: must be positive')

$out = $res->fold(fn (array $errs) => $errs, fn ($ok) => $ok);
```

---

Project status
--------------

[](#project-status)

- Current tag: **0.1.1** (custom exceptions, solid core)
- Next up:

    - **Docs**: keep expanding usage examples and the aliases guide
    - Nice-to-haves: `Either::sequence/traverse`, `IO::sequence/traverse`

---

Development
-----------

[](#development)

```
composer test       # phpunit
composer mutate     # infection --min-msi=100
```

CI runs on branch `canon`. Docs-only changes skip CI by default.

---

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

[](#contributing)

Issues and PRs welcome. Please include tests. If you’re fixing edge cases in `IO`/`Writer`, mutation tests are your friend.

---

License
-------

[](#license)

Apache-2.0 - see `LICENSE`.

###  Health Score

29

—

LowBetter than 60% of packages

Maintenance59

Moderate activity, may be stable

Popularity3

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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

Unknown

Total

1

Last Release

259d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/556010b8e36f70f2720a6a3e62719d70a157fc05b772efc5f99acdef1f0c4403?d=identicon)[RichardVasquez](/maintainers/RichardVasquez)

---

Top Contributors

[![RichardVasquez](https://avatars.githubusercontent.com/u/1706965?v=4)](https://github.com/RichardVasquez "RichardVasquez (16 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/penguinpark-monad/health.svg)

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

###  Alternatives

[anttiviljami/wp-libre-form

Easy native HTML5 forms for WordPress

664.5k](/packages/anttiviljami-wp-libre-form)[inkvizytor/fluentform

Form builder for Laravel

3416.2k](/packages/inkvizytor-fluentform)[globalis/robo-task

Robo task common collection

1225.4k2](/packages/globalis-robo-task)[dzentota/chrome-dev-tools

This is a PHP lib that allows one to interact with Google Chrome using Chrome DevTools Protocol within a PHP script. Highly inspired by marty90/PyChromeDevTools

126.4k](/packages/dzentota-chrome-dev-tools)

PHPackages © 2026

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