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

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

chippyash/monad
===============

Functional programming Monad support

3.0.1(4y ago)2828.1k↓50%58BSD-3-ClausePHPPHP &gt;=8.0

Since Jun 10Pushed 4y ago2 watchersCompare

[ Source](https://github.com/chippyash/Monad)[ Packagist](https://packagist.org/packages/chippyash/monad)[ Docs](http://zf4.biz/packages?utm_source=packagist&utm_medium=web&utm_campaign=blinks&utm_content=monad)[ RSS](/packages/chippyash-monad/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (2)Versions (23)Used By (8)

chippyash/Monad
===============

[](#chippyashmonad)

Quality Assurance
-----------------

[](#quality-assurance)

[![PHP 8.0](https://camo.githubusercontent.com/82887c22962d0022d20f61e17b76e949b7c813ca52cd0b00e794487a1a951169/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e302d626c75652e737667)](https://camo.githubusercontent.com/82887c22962d0022d20f61e17b76e949b7c813ca52cd0b00e794487a1a951169/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048502d382e302d626c75652e737667)[![Build Status](https://camo.githubusercontent.com/9cf0ce20cdec97462f6c71d3a121a9a6d6823f87505008806f424c28f34b5836/68747470733a2f2f7472617669732d63692e6f72672f6368697070796173682f4d6f6e61642e7376673f6272616e63683d6d6173746572)](https://travis-ci.org/chippyash/Monad)[![Test Coverage](https://camo.githubusercontent.com/54e08ca9ff1241ed26c38f029444a30e9cd10ec26ecfd2790b41dc216b2effb2/68747470733a2f2f636f6465636c696d6174652e636f6d2f6769746875622f6368697070796173682f4d6f6e61642f6261646765732f636f7665726167652e737667)](https://codeclimate.com/github/chippyash/Monad/coverage)[![Code Climate](https://camo.githubusercontent.com/38ff36f8d7b638f12579d2ccba134f3fa2e4fb2a98044c0a9c9a9dbd4d0151b5/68747470733a2f2f636f6465636c696d6174652e636f6d2f6769746875622f6368697070796173682f4d6f6e61642f6261646765732f6770612e737667)](https://codeclimate.com/github/chippyash/Monad)

The above badges represent the current development branch. As a rule, I don't push to GitHub unless tests, coverage and usability are acceptable. This may not be true for short periods of time; on holiday, need code for some other downstream project etc. If you need stable code, use a tagged version. Read 'Further Documentation' and 'Installation'.

[Test Contract](https://github.com/chippyash/Monad/blob/master/docs/Test-Contract.md) in the docs directory.

Developer support for PHP5.4 &amp; 5.5 was withdrawn at version 2.0.0 of this library. If you need support for PHP 5.4 or 5.5, please use a version`>=1,=2,bind(function($val, $mult){return $val * $mult;}, [4]);
```

Bear in mind that you can use the `use` clause as normal when defining your function to expose external parameter values. Caveat: start using this stuff in pure Async PHP programming and you can't use the `use` clause. You have been warned!

- value():mixed - the return() method as `return` is a reserved word in PHP

Additionally, two helper methods are defined for the interface

- flatten():mixed - the monadic value `flattened` to a PHP native type or non Monadic object
- static create(mixed $value):Monadic A factory method to create an instance of the concrete descendant Monad

Monads have an immutable value, that is to say, the result of the bind() method is another (Monadic) class. The original value is left alone.

### The Monad Abstract class

[](#the-monad-abstract-class)

Contains the Monad value holder and a `syntatic sugar` helper magic \_\_invoke() method that proxies to value() if no parameters supplied or bind() if a Closure (with/without optional arguments) are supplied.

Neither the Monadic interface or the abstract Monad class define how to set a value on construction of a concrete Monad. It usually makes sense to set the Monad's value on construction. Therefore in most circumstances you would create concrete Monad classes with some form of constructor.

### Concrete Monad classes supplied

[](#concrete-monad-classes-supplied)

#### Identity

[](#identity)

The simplest type of Monad

```
use Monad\Identity;

$id = new Identity('foo');
//or
$id = Identity::create('foo');

$fConcat = function($value, $fudge){return $value . $fudge};
$concat = $id->bind($fConcat, ['bar'])
             ->bind($fConcat, ['baz']);

echo $concat->value();      //'foobarbaz'
echo $id->value();          //'foo'
```

#### Option

[](#option)

An Option is a polymorphic `Maybe Monad` that can exist in one of two states:

- Some - an option with a value
- None - an option with no value

As PHP does not have the language construct to create a polymorphic object by construction, you'll need to use the Option::create() static method. You can however use Option as a type hint for other class methods and returns

```
use Monad\Option;
use Monad\Option\Some;
use Monad\Option\None;

/**
 * @param Option $opt
 * @return Option
 */
function doSomethingWithAnOption(Option $opt) {
    if ($opt instanceof None) {
        return $opt;
    }

    //must be a Some
    return $opt(doMyOtherThing()); //use magic invoke to bind

}

$someOption = Option::create('foo');
$noneOption = Option::create();

$one = doSomethingWithAnOption($someOption);
$two = doSomethingWithAnOption($noneOption);
```

Under normal circumstances, Option uses the `null` value to determine whether or not to create a Some or a None; that is, the value passed into create() is tested against `null`. If it === null, then a None is created, else a Some. You can provide an alternative test value as a second parameter to create()

```
$mySome = Option::create(true, false);
$myNone = Option::create(false, false);
```

Once a None, always a None. No amount of binding will return anything other than a None.
On the other hand, a Some can become a None through binding, (or rather the result of the bind() as of course the original Some remains immutable.) To assist in this, Some-&gt;bind() can take an optional third parameter, which is the value to test against for None (i.e. like the optional second parameter to Option::create() )

You should also note that calling -&gt;value() on a None will generate a RuntimeException because of course, a None does not have a value!

##### Other methods supported

[](#other-methods-supported)

- getOrElse(mixed:elseValue) If the Option is a Some, return the Option-&gt;value() else return the elseValue

#### FTry

[](#ftry)

An FTry is a polymorphic `Try Monad` that can exist in one of two states:

- Success - an FTry with a value
- Failure - an FTry with a PHP Exception as a value

`Try` is a reserved word in PHP, so I have called this class FTry to mean `Functional Try`.

As PHP does not have the language construct to create a polymorphic object by construction, you'll need to use the FTry::with() (or FTry::create()) static method. You can however use FTry as a type hint for other class methods and returns

FTry::on(value) will catch any Exception incurred in processing the value, and return a Success or Failure class appropriately. This makes it ideal for the simple case of wrapping a PHP transaction in a Try - Catch block:

```
use Monad\FTry;
use Monad\FMatch;

FMatch::on(FTry::with($myFunction($initialValue())))
    ->Monad_FTry_Success(function ($v) {doSomethingGood($v);})
    ->Monad_FTry_Failure(
        function (\Exception $e) {
            echo "Exception: " . $e->getMessage();
        }
    );
```

A fairly simplistic example, and one where you might question its value, as it could have been written as easily using conventional PHP. But: A Success or Failure is still a Monad, and so you can still bind (map) onto the resultant class, flatten it etc.

Like Option, FTry also supports the `getOrElse(mixed:elseValue)` method allowing for implementing default behaviours:

```
echo FTry::with(myComplexPrintableTransaction())
    ->getOrElse('Sorry - that failed');
```

For completeness, FTry also supports `isSuccess()`:

```
echo 'The colour is' . FTry::with(myTest())->isSuccess() ? 'blue' : 'red';
```

Once a Failure, always a Failure. However, A Success can yield either a Success or a Failure as a result of binding.

If you really want to throw the exception contained in a Failure use the `pass()` method

```
$try = FTry::with($myFunction());
if (!$try->isSuccess()) $try->pass();
```

#### FMatch

[](#fmatch)

The FMatch Monad allows you to carry out type pattern matching to create powerful and dynamic functional equivalents of `case statements`.

'Match' is a reserved word since PHP8. Thus for V3 of this library, I've renamed Match to FMatch.

The basic syntax is

```
use Monad\FMatch;

$result = FMatch::on($initialValue)
            ->test()
            ->test()
            ->value();
```

where test() can be the name of a native PHP type or the name of a class, e.g.:

```
$result = FMatch::on($initialValue)
            ->string()
            ->Monad_Option()
            ->Monad_Identity()
            ->value()
```

You can use the FMatch::any() method to catch anything not matched by a specific matcher:

```
$result = FMatch::on($initialValue)
            ->string()
            ->int()
            ->any()
            ->value();
```

You can provide a concrete value as a parameter to each test, or a function. e.g.

```
$result = FMatch::on($initialValue)
              ->string('foo')
              ->Monad_Option(
                  function ($v) {
                      return FMatch::on($v)
                          ->Monad_Option_Some(function ($v) {
                              return $v->value();
                          })
                          ->Monad_Option_None(function () {
                              throw new \Exception();
                          })
                          ->value();
                      }
              )
              ->Monad_Identity(
                  function ($v) {
                      return $v->value() . 'bar';
                  }
              )
              ->any(function(){return 'any';})
              ->value();
```

You can find this being tested in FMatchTest::testYouCanNestFMatches()

##### Supported native type matches

[](#supported-native-type-matches)

- string
- integer|int|long
- float|double|real
- null
- array
- bool|boolean
- callable|function|closure
- file
- dir|directory
- object
- scalar
- numeric
- resource

##### Supported class matching

[](#supported-class-matching)

Use the fully namespaced name of the class to match, substituting the backslash \\ with an underscore e.g. to test for `Monad\Option` use `Monad_Option`

#### Collection

[](#collection)

The Monad Collection provides a structured array that behaves as a Monad. It is based on the SPL ArrayObject.

Very important to note however is that unlike a PHP array, the Collection is type specific, i.e. you specify Collection type specifically or by default as the first member of its construction array.

Another 'gotcha': As the Collection is an object, calling Collection-&gt;value() will just return the Collection itself. If you want to get a PHP array from the Collection then use `toArray()` which proxies the underlying `getArrayCopy()` and is provided as most PHPers are familiar with `toArray` as being a missing 'magic' call.

Why re-invent the wheel? ArrayObject (underpinning Collection,) behaves in subtly different ways than a plain vanilla array. One: it's an object and can therefore be passed by reference, Two: because of One, it (hopefully TBC,) stops segfaults occurring in a multi thread environment. Even if Two doesn't pan out, then One still holds.

```
use Monad\Collection;

$c = Collection::create([1,2,3,4]);
//or
$c = Collection::create([1,2,3,4], 'integer');

//to create an empty collection, you must specify type
$c = Collection::create([], 'integer');
$c = Collection::create([], 'Monad\Option');
```

You can get and test a Collection:

```
$c = Collection::create([1,2,3,4]);
$v = $c[2] // == 3

if (!isset($c[6]) {
...
}
```

Although the Collection implements the ArrayAccess interface, trying to set or unset a value `$mCollection[0] = 'foo'` or `unset($mCollection[0])` *will* throw an exception, as Collections are *immutable* by default. In some circumstances, you may want to change this. Use the MutableCollection to allow mutability.

As usual, this is not really a problem, as you can bind() or use each() on a Collection to return another Collection, (which can contain values of a different type.)
Wherever possible, I've expressed the Collection implementation in terms of FMatch statements, not only because it usually means tighter code, but as something that you can look at (and criticise hopefully!) by example.

You can append to a Collection, returning a new Collection

```
$s1 = new Collection([1,2,3]);
$s2 = $s1->append(4);
//or
$s2 = $s1->append(['foo'=>4]);
```

You can get the difference of two collections:

```
$s1 = Collection::create([1, 2, 3, 6, 7]);
$s2 = Collection::create([6,7]);
$s3 = $s1->vDiff($s2);  //difference on values
$s4 = $s1->kDiff($s2);  //difference on keys
```

And the intersection:

```
$s1 = Collection::create([1, 2, 3, 6, 7]);
$s2 = Collection::create([6,7]);
$s3 = $s1->vIntersect($s2); //intersect on values
$s4 = $s1->kIntersect($s2); //intersect on keys
```

`uDiff`, `kDiff`, `vIntersect` and `kIntersect` can take a second optional Closure parameter which is used as the comparator method.

You can get the union of two collections, either by value or key:

```
$s1 = Collection::create([1, 2, 3, 6, 7]);
$s2 = Collection::create([3, 6, 7, 8]);
$valueUnion = $s1->vUnion($s2);
$keyUnion =  $s1->kUnion($s2);
```

You can get the head and the tail of a collection:

```
$s1 = Collection::create([1, 2, 3, 6, 7]);
echo $s1->head()[0] // 1
echo $s1->tail()[0] // 2
echo $s1->tail()[3] // 7
```

There are four function mapping methods for a Collection:

- the standard Monadic bind(), whose function takes the entire `value array` of the Collection as its parameter. You should return an array as a result of the function but in the event that you do not, it will be forced to a Collection.
- the each() method. Like bind(), this takes a function and an optional array of additional parameter values to pass on. However, the each function is called for each member of the collection. The results of the function are collected into a new Collection and returned. In this way, it behaves rather like the PHP native array\_map.
- the reduce() method. Acts just like array\_reduce and returns a single value as a result of function passed in as a paramter.
- the filter() method. Acts just like array\_filter, but returns a new Collection as a result of the reduction.

Note that you can change the base type of a resultant Collection as a result of these mapping methods().

I chose Collection as the name as it doesn't clash with `list` which is a PHP reserved name. In essence, Collection will to all intents and purposes be a List, but for die hard PHPers still behave as an array.

A secondary design consideration, is that you should be able to use Collection oblivious of that fact that it is a Monad, except that it is type specific.

#### Map

[](#map)

A Map is a simple extension of a Collection that requires its entries to have a string (hash) key. It obeys all the rules of a Collection except that

```
use Monad/Map;

$m1 = new Map(['foo']);
```

will not work, but

```
$m1 = new Map(['foo'=>'bar']);
```

will work. You can as usual, specify the type as a second parameter. The `vUnion`, `vIntersect` and `vDiff` methods are unspecified for Maps and will throw a `BadMethodCallException`.

#### Set

[](#set)

A Set is a simple extension of Collection that enforces the following rules

- A Set can only have unique values (of the same type)
- A Set doesn't care about the keys, its the values that are important
- Operations on a Set return a Set

```
use Monad/Set;

$setA = new Set(['a','b','c']);
$setB = new Set(['a','c']);

$setC = $setA->vIntersect($setB);
$setD = $setA->vUnion($setB);
$setE = $setA->vDiff($setB);
```

As with a Collection, you can specify an empty construction value and a second type value. You can also append to a Set to return a new Set.

The `kUnion`, `kIntersect` and `kDiff` methods are unspecified for Maps and will throw a `BadMethodCallException`.

All other Collection methods are supported, returning Sets where expected.

The -&gt;vIntersect(), -&gt;vUnion() and -&gt;diff() methods all accept a second equality function parameter as per Collection. However, for Set, if none is provided it will default to using a sane default, that is to casting non stringifiable values to a serialized hash of the value and using that for comparison. Supply your own functions if this default is inadequate for your purposes.

Further documentation
---------------------

[](#further-documentation)

Please note that what you are seeing of this documentation displayed on Github is always the latest dev-master. The features it describes may not be in a released version yet. Please check the documentation of the version you Compose in, or download.

[Test Contract](https://github.com/chippyash/Monad/blob/master/docs/Test-Contract.md) in the docs directory.

Check out [ZF4 Packages](http://zf4.biz/packages?utm_source=github&utm_medium=web&utm_campaign=blinks&utm_content=monad) for more packages

### UML

[](#uml)

[![class diagram](https://github.com/chippyash/Monad/raw/master/docs/monad-classes.png)](https://github.com/chippyash/Monad/blob/master/docs/monad-classes.png)

Changing the library
--------------------

[](#changing-the-library)

1. fork it
2. write the test
3. amend it
4. do a pull request

Found a bug you can't figure out?

1. fork it
2. write the test
3. do a pull request

NB. Make sure you rebase to HEAD before your pull request

Or - raise an issue ticket.

Where?
------

[](#where)

The library is hosted at [Github](https://github.com/chippyash/Monad). It is available at [Packagist.org](https://packagist.org/packages/chippyash/monad)

### Installation

[](#installation)

Install [Composer](https://getcomposer.org/)

#### For production

[](#for-production)

```
    "chippyash/monad": "~3.0"
```

Or to use the latest, possibly unstable version:

```
    "chippyash/monad": "dev-master"
```

#### For development

[](#for-development)

Clone this repo, and then run Composer in local repo root to pull in dependencies

```
    git clone git@github.com:chippyash/Monad.git Monad
    cd Monad
    composer install
```

To run the tests:

```
    cd Monad
    vendor/bin/phpunit -c test/phpunit.xml test/
```

##### Debugging

[](#debugging)

Because PHP doesn't really support functional programming at it's core level, debugging using XDebug etc becomes a nested mess. Some things I've found helpful:

- isolate your tests, at least at the initial stage. If you get a problem, create a test that does one thing - the thing you are trying to debug. Use that as your start point.
- be mindful of value() and flatten(), the former gets the immediate Monad value, the latter gives you a PHP fundamental.
- when constructing FMatches, ensure the value contained in the FMatch conforms to the type you are expecting. Remember, FMatch returns a FMatch with a value. And yes, I've tripped up on this myself.
- keep running the other tests. Seems simple, but in the headlong pursuit of your single objective, it's easy to forget that the library is interdependent (and will become increasingly so as we are able to wrap new functionality back into the original code. e.g. Collection is dependent on FMatch: when FFor is implemented, FMatch will change.) Run the whole test suite on a regular basis. That way you catch anything that has broken upstream functionality. This library will be complete when it, as far as possible, expresses itself in terms of itself!
- the tests that are in place are there for a good reason: open an issue if you think they are wrong headed, misguided etc

License
-------

[](#license)

This software library is released under the [BSD 3 Clause license](https://opensource.org/licenses/BSD-3-Clause)

This software library is Copyright (c) 2015,2021 Ashley Kitson, UK

This software library contains code items that are derived from other works:

None of the contained code items breaks the overriding license, or vice versa, as far as I can tell. If at all unsure, please seek appropriate advice.

If the original copyright owners of the derived code items object to this inclusion, please contact the author.

Thanks
------

[](#thanks)

I didn't do this by myself. I'm deeply indebted to those that trod the path before me.

The following have done work on which this library is based:

[Sean Crystal](https://github.com/spiralout/Phonads)

[Anthony Ferrara](http://blog.ircmaxell.com/2013/07/taking-monads-to-oop-php.html)

[Johannes Schmidt](https://github.com/schmittjoh/php-option)

History
-------

[](#history)

V1.0.0 Initial Release

V1.1.0 Added FTry

V1.2.0 Added Collection

V1.2.1 fixes on Collection

V1.2.2 add sort order for vUnion method

V1.2.3 allow descendent monadic types

V1.2.4 add each() method to Collection

V1.2.5 move from coveralls to codeclimate

V1.2.6 Add link to packages

V1.2.7 Code cleanup - verify PHP7 compatibility

V1.3.0 Collection is immutable. Added MutableCollection for convenience

V1.4.0 Add Map class - enforced string type keys for collection members

```
   Add convenience method append() to Collection === ->vUnion(new Collection([$nValue]))

```

V1.5.0 Add Set class

V1.5.1 Add additional checking for Maps and Sets. diff() and intersect() deprecated, use the kDiff(), uDiff, kIntersect() and uIntersect() methods;

V1.5.2 build script update

V1.5.3 update composer - forced by packagist composer.json format change

V2.0.0 BC Break. Support for PHP &lt;5.6 withdrawn

V2.0.1 fixes for PHP &gt;= 7.1

V2.1.0 Change of license from GPL V3 to BSD 3 Clause

V2.1.1 Flatten value in the bind method of FTry, so in case the binded function returns a Success, we do not end up with nested Success. PR by [josselinauguste](https://github.com/josselinauguste)

V3.0.0 BC Break. Support for PHP &lt;8 withdrawn. Match renamed to FMatch.

###  Health Score

43

—

FairBetter than 91% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity37

Limited adoption so far

Community24

Small or concentrated contributor base

Maturity78

Established project with proven stability

 Bus Factor1

Top contributor holds 90.8% 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 ~103 days

Recently: every ~280 days

Total

22

Last Release

1821d ago

Major Versions

1.5.3 → 2.0.02018-04-03

2.1.1 → 3.0.02021-05-23

PHP version history (3 changes)1.0.0PHP &gt;=5.4

2.0.0PHP &gt;=5.6

3.0.0PHP &gt;=8.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/198575568597b367c8b285de16d278018c8cf292c6c75c535270135c1eea0561?d=identicon)[chippyash](/maintainers/chippyash)

---

Top Contributors

[![chippyash](https://avatars.githubusercontent.com/u/983560?v=4)](https://github.com/chippyash "chippyash (69 commits)")[![akitson-fu](https://avatars.githubusercontent.com/u/19702356?v=4)](https://github.com/akitson-fu "akitson-fu (3 commits)")[![akzincsystems](https://avatars.githubusercontent.com/u/73334506?v=4)](https://github.com/akzincsystems "akzincsystems (2 commits)")[![josselinauguste](https://avatars.githubusercontent.com/u/458535?v=4)](https://github.com/josselinauguste "josselinauguste (1 commits)")[![nawie](https://avatars.githubusercontent.com/u/1515597?v=4)](https://github.com/nawie "nawie (1 commits)")

---

Tags

identitycollectionoptionfunctionalMatchmonadtry

###  Code Quality

TestsPHPUnit

### Embed Badge

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

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

###  Alternatives

[phpoption/phpoption

Option Type for PHP

2.7k541.2M159](/packages/phpoption-phpoption)

PHPackages © 2026

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