{"id":20254756,"url":"https://github.com/chippyash/monad","last_synced_at":"2025-04-11T00:03:57.207Z","repository":{"id":33355784,"uuid":"37000591","full_name":"chippyash/Monad","owner":"chippyash","description":"Functional Monads for PHP","archived":false,"fork":false,"pushed_at":"2021-05-23T07:47:09.000Z","size":721,"stargazers_count":27,"open_issues_count":0,"forks_count":5,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-11T00:03:47.587Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chippyash.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-06-07T01:30:51.000Z","updated_at":"2024-09-25T01:11:04.000Z","dependencies_parsed_at":"2022-09-03T23:31:54.348Z","dependency_job_id":null,"html_url":"https://github.com/chippyash/Monad","commit_stats":null,"previous_names":[],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chippyash%2FMonad","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chippyash%2FMonad/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chippyash%2FMonad/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chippyash%2FMonad/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chippyash","download_url":"https://codeload.github.com/chippyash/Monad/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248317705,"owners_count":21083528,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-14T10:34:45.880Z","updated_at":"2025-04-11T00:03:57.148Z","avatar_url":"https://github.com/chippyash.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# chippyash/Monad\n\n## Quality Assurance\n\n![PHP 8.0](https://img.shields.io/badge/PHP-8.0-blue.svg)\n[![Build Status](https://travis-ci.org/chippyash/Monad.svg?branch=master)](https://travis-ci.org/chippyash/Monad)\n[![Test Coverage](https://codeclimate.com/github/chippyash/Monad/badges/coverage.svg)](https://codeclimate.com/github/chippyash/Monad/coverage)\n[![Code Climate](https://codeclimate.com/github/chippyash/Monad/badges/gpa.svg)](https://codeclimate.com/github/chippyash/Monad)\n\nThe above badges represent the current development branch.  As a rule, I don't push\n to GitHub unless tests, coverage and usability are acceptable.  This may not be\n true for short periods of time; on holiday, need code for some other downstream\n project etc.  If you need stable code, use a tagged version. Read 'Further Documentation'\n and 'Installation'.\n \n [Test Contract](https://github.com/chippyash/Monad/blob/master/docs/Test-Contract.md) in the docs directory.\n \nDeveloper support for PHP5.4 \u0026 5.5 was withdrawn at version 2.0.0 of this library.\nIf you need support for PHP 5.4 or 5.5, please use a version`\u003e=1,\u003c2`\n\nDeveloper support for PHP \u003c8 was withdrawn at version 3.0.0 of this library.\nIf you need support for PHP 7.x, please use a version`\u003e=2,\u003c3`\n \n## What?\n\nProvides a Monadic type\n\nAccording to my mentor, Monads are either difficult to explain or difficult to code, \ni.e. you can say `how` or `what` but not at the same time. If\nyou need further illumination, start with [wikipedia](https://en.wikipedia.org/wiki/Monad_(functional_programming))\n\n### Types supported\n\n* Monadic Interface\n* Abstract Monad\n* Identity Monad\n* Option Monad\n    * Some\n    * None\n* FTry Monad\n    * Success\n    * Failure\n* FMatch Monad\n* Collection Monad\n* Map Monad\n* Set Monad\n\n## Why?\n\nPHP is coming under increasing attack from functional hybrid languages such as Scala.\nThe difference is the buzzword of `functional programming`. PHP can support this \nparadigm, and this library introduces some basic monadic types. Indeed, learning\nfunctional programming practices can make solutions in PHP far more robust. \n\nMuch of the power of monadic types comes through the use of the functional FMatch, \nTry and For Comprehension language constructs.  PHP doesn't have these. This library provides:\n\n- FMatch\n- FTry\n- FFor This is provided in the [Assembly-Builder package](https://github.com/chippyash/Assembly-Builder)\n \nKey to functional programming is the use of strict typing and elevating functions as\nfirst class citizens within the language syntax. PHP5.4+ allows functions to be used as\na typed parameter (Closure). It also appears that PHP devs are coming to terms with\nstrict or hard types as the uptake of my [strong-type library](https://packagist.org/packages/chippyash/strong-type) testifies.\n\n## How\n\n### The Monadic interface\n\nA Monad has three things (according to my understanding of it):\n\n- a value (which may be no value at all, a simple type, an object or a function)\n- method of getting its value, often referred to as return()\n- a way of binding (or using) the value into some function, often referred to as  bind(), \nthe return value of which is another Monad, often but not always of the same type as \nthe donor Monad. (Rarely, it could be another class type.)\n\nThe Monadic Interface supplied here defines\n\n- bind(\\Closure $function, array $args = []):Monadic\n  - The function signature should contain at least one parameter to receive the value\n   of the Monad. e.g. `function($val){return $val * 2;}` If you are using additional\n   arguments in the $args array, you'll need to add them to the parameter list e.g.\n\u003cpre\u003e\n$ret = $m-\u003ebind(function($val, $mult){return $val * $mult;}, [4]);\n\u003c/pre\u003e\n  Bear in mind that you can use the `use` clause as normal when defining your function \n    to expose external parameter values. Caveat: start using this stuff in pure Async\n    PHP programming and you can't use the `use` clause. You have been warned!\n    \n    \n- value():mixed - the return() method as `return` is a reserved word in PHP\n\nAdditionally, two helper methods are defined for the interface\n\n- flatten():mixed - the monadic value `flattened` to a PHP native type or non Monadic object\n- static create(mixed $value):Monadic A factory method to create an instance of the concrete descendant Monad\n\nMonads have an immutable value, that is to say, the result of the bind()\nmethod is another (Monadic) class.  The original value is left alone.\n\n### The Monad Abstract class\n\nContains the Monad value holder and a `syntatic sugar` helper magic \\__invoke() method that \nproxies to value() if no parameters supplied or bind() if a Closure (with/without optional arguments)\nare supplied.\n\nNeither the Monadic interface or the abstract Monad class define how to set a value on\nconstruction of a concrete Monad.  It usually makes sense to set the Monad's value on construction.\nTherefore in most circumstances you would create concrete Monad classes with some\nform of constructor.\n \n### Concrete Monad classes supplied\n\n#### Identity\nThe simplest type of Monad\n\n\u003cpre\u003e\nuse Monad\\Identity;\n\n$id = new Identity('foo');\n//or\n$id = Identity::create('foo');\n\n$fConcat = function($value, $fudge){return $value . $fudge};\n$concat = $id-\u003ebind($fConcat, ['bar'])\n             -\u003ebind($fConcat, ['baz']);\n\necho $concat-\u003evalue();      //'foobarbaz'\necho $id-\u003evalue();          //'foo'\n\u003c/pre\u003e\n\n#### Option\n\nAn Option is a polymorphic `Maybe Monad` that can exist in one of two states:\n\n- Some - an option with a value\n- None - an option with no value\n\nAs PHP does not have the language construct to create a polymorphic object by construction,\nyou'll need to use the Option::create() static method.  You can however use Option as \na type hint for other class methods and returns\n\n\u003cpre\u003e\nuse Monad\\Option;\nuse Monad\\Option\\Some;\nuse Monad\\Option\\None;\n\n/**\n * @param Option $opt\n * @return Option\n */\nfunction doSomethingWithAnOption(Option $opt) {\n    if ($opt instanceof None) {\n        return $opt;\n    }\n    \n    //must be a Some\n    return $opt(doMyOtherThing()); //use magic invoke to bind\n\n}\n\n$someOption = Option::create('foo');\n$noneOption = Option::create();\n\n$one = doSomethingWithAnOption($someOption);\n$two = doSomethingWithAnOption($noneOption);\n\u003c/pre\u003e\n\nUnder normal circumstances, Option uses the `null` value to determine whether or not\n to create a Some or a None; that is, the value passed into create() is tested against\n `null`. If it === null, then a None is created, else a Some.  You can provide an\n alternative test value as a second parameter to create()\n \n\u003cpre\u003e\n$mySome = Option::create(true, false);\n$myNone = Option::create(false, false);\n\u003c/pre\u003e\n\nOnce a None, always a None. No amount of binding will return anything other than a None.  \nOn the other hand, a Some can become a None through binding, (or rather the result of \nthe bind() as of course the original Some remains immutable.)  To assist in this,\nSome-\u003ebind() can take an optional third parameter, which is the value to test against\nfor None (i.e. like the optional second parameter to Option::create() )\n\nYou should also note that calling -\u003evalue() on a None will generate a RuntimeException\nbecause of course, a None does not have a value!\n\n##### Other methods supported\n\n* getOrElse(mixed:elseValue) If the Option is a Some, return the Option-\u003evalue() else\n    return the elseValue\n    \n#### FTry\n\nAn FTry is a polymorphic `Try Monad` that can exist in one of two states:\n\n- Success - an FTry with a value\n- Failure - an FTry with a PHP Exception as a value\n\n`Try` is a reserved word in PHP, so I have called this class FTry to mean `Functional Try`.\n\nAs PHP does not have the language construct to create a polymorphic object by construction,\nyou'll need to use the FTry::with() (or FTry::create()) static method.  You can however \nuse FTry as a type hint for other class methods and returns\n\nFTry::on(value) will catch any Exception incurred in processing the value, and return\na Success or Failure class appropriately.  This makes it ideal for the simple case\nof wrapping a PHP transaction in a Try - Catch block:\n \n\u003cpre\u003e\nuse Monad\\FTry;\nuse Monad\\FMatch;\n\nFMatch::on(FTry::with($myFunction($initialValue())))\n    -\u003eMonad_FTry_Success(function ($v) {doSomethingGood($v);})\n    -\u003eMonad_FTry_Failure(\n        function (\\Exception $e) {\n            echo \"Exception: \" . $e-\u003egetMessage(); \n        }\n    );\n\u003c/pre\u003e\n\nA fairly simplistic example, and one where you might question its value, as it could have\n been written as easily using conventional PHP.  But: A Success or Failure is still \n a Monad, and so you can still bind (map) onto the resultant class, flatten it etc.\n \nLike Option, FTry also supports the `getOrElse(mixed:elseValue)` method allowing for implementing\ndefault behaviours:\n\n\u003cpre\u003e\necho FTry::with(myComplexPrintableTransaction())\n    -\u003egetOrElse('Sorry - that failed');\n\u003c/pre\u003e\n\nFor completeness, FTry also supports `isSuccess()`:\n\n\u003cpre\u003e\necho 'The colour is' . FTry::with(myTest())-\u003eisSuccess() ? 'blue' : 'red';\n\u003c/pre\u003e\n\nOnce a Failure, always a Failure.  However, A Success can yield either a Success\nor a Failure as a result of binding.\n\nIf you really want to throw the exception contained in a Failure use the `pass()` method\n\n\u003cpre\u003e\n$try = FTry::with($myFunction());\nif (!$try-\u003eisSuccess()) $try-\u003epass();\n\u003c/pre\u003e\n\n#### FMatch\n\nThe FMatch Monad allows you to carry out type pattern matching to create powerful and \ndynamic functional equivalents of `case statements`.\n\n'Match' is a reserved word since PHP8. Thus for V3 of this library, I've\nrenamed Match to FMatch.\n\nThe basic syntax is\n\n\u003cpre\u003e\nuse Monad\\FMatch;\n\n$result = FMatch::on($initialValue)\n            -\u003etest()\n            -\u003etest()\n            -\u003evalue();\n\u003c/pre\u003e\n\nwhere test() can be the name of a native PHP type or the name of a class, e.g.:\n\n\u003cpre\u003e\n$result = FMatch::on($initialValue)\n            -\u003estring()\n            -\u003eMonad_Option()\n            -\u003eMonad_Identity()\n            -\u003evalue()\n\u003c/pre\u003e\n\nYou can use the FMatch::any() method to catch anything not matched by a specific matcher:\n\n\u003cpre\u003e\n$result = FMatch::on($initialValue)\n            -\u003estring()\n            -\u003eint()\n            -\u003eany()\n            -\u003evalue();\n\u003c/pre\u003e\n\nYou can provide a concrete value as a parameter to each test, or a function. e.g.\n\n\u003cpre\u003e\n$result = FMatch::on($initialValue)\n              -\u003estring('foo')\n              -\u003eMonad_Option(\n                  function ($v) {\n                      return FMatch::on($v)\n                          -\u003eMonad_Option_Some(function ($v) {\n                              return $v-\u003evalue();\n                          })\n                          -\u003eMonad_Option_None(function () {\n                              throw new \\Exception();\n                          })\n                          -\u003evalue();\n                      }\n              )\n              -\u003eMonad_Identity(\n                  function ($v) {\n                      return $v-\u003evalue() . 'bar';\n                  }\n              )\n              -\u003eany(function(){return 'any';})\n              -\u003evalue();\n\u003c/pre\u003e\n\nYou can find this being tested in FMatchTest::testYouCanNestFMatches()\n\n##### Supported native type matches\n\n- string\n- integer|int|long\n- float|double|real\n- null\n- array\n- bool|boolean\n- callable|function|closure\n- file\n- dir|directory\n- object\n- scalar\n- numeric\n- resource\n\n##### Supported class matching\n\nUse the fully namespaced name of the class to match, substituting the backslash \\\\\nwith an underscore e.g. to test for `Monad\\Option` use `Monad_Option`\n\n#### Collection\n\nThe Monad Collection provides a structured array that behaves as a Monad.  It is based\non the SPL ArrayObject.\n\nVery important to note however is that unlike a PHP array, the Collection is type \nspecific, i.e. you specify Collection type specifically or by default as the first member \nof its construction array.  \n\nAnother 'gotcha': As the Collection is an object, calling Collection-\u003evalue() will\n just return the Collection itself. If you want to get a PHP array from the Collection\n then use `toArray()` which proxies the underlying `getArrayCopy()` and is provided\n as most PHPers are familiar with `toArray` as being a missing 'magic' call.\n \nWhy re-invent the wheel? ArrayObject (underpinning Collection,) behaves in subtly \n different ways than a plain vanilla array. One: it's an object and can therefore\n be passed by reference, Two: because of One, it (hopefully TBC,) stops segfaults\n occurring in a multi thread environment.  Even if Two doesn't pan out, then One still\n  holds.\n \n\u003cpre\u003e\nuse Monad\\Collection;\n\n$c = Collection::create([1,2,3,4]);\n//or\n$c = Collection::create([1,2,3,4], 'integer');\n\n//to create an empty collection, you must specify type\n$c = Collection::create([], 'integer');\n$c = Collection::create([], 'Monad\\Option');\n\u003c/pre\u003e\n\nYou can get and test a Collection:\n \n\u003cpre\u003e\n$c = Collection::create([1,2,3,4]);\n$v = $c[2] // == 3\n\nif (!isset($c[6]) { \n... \n}\n\u003c/pre\u003e\n \nAlthough the Collection implements the ArrayAccess interface, trying to set or unset \na value `$mCollection[0] = 'foo'` or `unset($mCollection[0])` *will* throw an \nexception, as Collections are *immutable* by default.  In some circumstances, you\nmay want to change this.  Use the MutableCollection to allow mutability. \n \nAs usual, this is not really a problem, as you can bind() or use each() on a Collection\n to return  another Collection, (which can contain values of a different type.)  \n Wherever possible, I've expressed the Collection implementation in terms of FMatch \n statements, not only because it usually means tighter code, but as something that \n you can look at (and criticise hopefully!) by example.\n\nYou can append to a Collection, returning a new Collection\n\n\u003cpre\u003e\n$s1 = new Collection([1,2,3]);\n$s2 = $s1-\u003eappend(4);\n//or\n$s2 = $s1-\u003eappend(['foo'=\u003e4]);\n\u003c/pre\u003e\n\nYou can get the difference of two collections:\n\n\u003cpre\u003e\n$s1 = Collection::create([1, 2, 3, 6, 7]);\n$s2 = Collection::create([6,7]);\n$s3 = $s1-\u003evDiff($s2);  //difference on values\n$s4 = $s1-\u003ekDiff($s2);  //difference on keys\n\u003c/pre\u003e\n\nAnd the intersection:\n\n\u003cpre\u003e\n$s1 = Collection::create([1, 2, 3, 6, 7]);\n$s2 = Collection::create([6,7]);\n$s3 = $s1-\u003evIntersect($s2); //intersect on values\n$s4 = $s1-\u003ekIntersect($s2); //intersect on keys\n\u003c/pre\u003e\n\n`uDiff`, `kDiff`, `vIntersect` and `kIntersect` can take a second optional Closure parameter which is used\nas the comparator method.\n\nYou can get the union of two collections, either by value or key:\n\n\u003cpre\u003e\n$s1 = Collection::create([1, 2, 3, 6, 7]);\n$s2 = Collection::create([3, 6, 7, 8]);\n$valueUnion = $s1-\u003evUnion($s2);\n$keyUnion =  $s1-\u003ekUnion($s2);\n\u003c/pre\u003e\n\nYou can get the head and the tail of a collection:\n\n\u003cpre\u003e\n$s1 = Collection::create([1, 2, 3, 6, 7]);\necho $s1-\u003ehead()[0] // 1\necho $s1-\u003etail()[0] // 2\necho $s1-\u003etail()[3] // 7\n\u003c/pre\u003e\n\nThere are four function mapping methods for a Collection:\n\n- the standard Monadic bind(), whose function takes the entire `value array` of the \nCollection as its parameter. You should return an array as a result of the function\nbut in the event that you do not, it will be forced to a Collection.\n\n- the each() method.  Like bind(), this takes a function and an optional array of \nadditional parameter values to pass on.  However, the each function is called for\neach member of the collection.  The results of the function are collected into a new \nCollection and returned.  In this way, it behaves rather like the PHP native array_map.\n\n- the reduce() method.  Acts just like array_reduce and returns a single value as a result\nof function passed in as a paramter.\n\n- the filter() method. Acts just like array_filter, but returns a new Collection as a \nresult of the reduction.\n\nNote that you can change the base type of a resultant Collection as a result of these \nmapping methods().\n\nI chose Collection as the name as it doesn't clash with `list` which is a PHP reserved name.\nIn essence, Collection will to all intents and purposes be a List, but for die hard PHPers\nstill behave as an array.\n\nA secondary design consideration, is that you should be able to use Collection \noblivious of that fact that it is a Monad, except that it is type specific.\n\n#### Map\n\nA Map is a simple extension of a Collection that requires its entries to have a string (hash)\n key. It obeys all the rules of a Collection except that \n \n\u003cpre\u003e\nuse Monad/Map;\n\n$m1 = new Map(['foo']);\n\u003c/pre\u003e\n\nwill not work, but\n\n\u003cpre\u003e\n$m1 = new Map(['foo'=\u003e'bar']);\n\u003c/pre\u003e\n\nwill work.  You can as usual, specify the type as a second parameter. The \n`vUnion`, `vIntersect` and `vDiff` methods are unspecified for Maps and will throw a \n`BadMethodCallException`.\n\n#### Set\n\nA Set is a simple extension of Collection that enforces the following rules\n\n- A Set can only have unique values (of the same type)\n- A Set doesn't care about the keys, its the values that are important\n- Operations on a Set return a Set\n\n\u003cpre\u003e\nuse Monad/Set;\n\n$setA = new Set(['a','b','c']);\n$setB = new Set(['a','c']);\n\n$setC = $setA-\u003evIntersect($setB);\n$setD = $setA-\u003evUnion($setB);\n$setE = $setA-\u003evDiff($setB);\n\u003c/pre\u003e\n\nAs with a Collection, you can specify an empty construction value and a second type value.\nYou can also append to a Set to return a new Set.\n\nThe `kUnion`, `kIntersect` and `kDiff` methods are unspecified for Maps and will throw a \n`BadMethodCallException`.\n\nAll other Collection methods are supported, returning Sets where expected.\n \nThe -\u003evIntersect(), -\u003evUnion() and -\u003ediff() methods all accept a second equality function\nparameter as per Collection.  However, for  Set, if none is provided it will default\nto using a sane default, that is to casting non stringifiable values to a serialized\nhash of the value and using that for comparison.  Supply your own functions if this\ndefault is inadequate for your purposes.\n\n## Further documentation\n\nPlease note that what you are seeing of this documentation displayed on Github is\nalways the latest dev-master. The features it describes may not be in a released version\n yet. Please check the documentation of the version you Compose in, or download.\n\n[Test Contract](https://github.com/chippyash/Monad/blob/master/docs/Test-Contract.md) in the docs directory.\n\nCheck out [ZF4 Packages](http://zf4.biz/packages?utm_source=github\u0026utm_medium=web\u0026utm_campaign=blinks\u0026utm_content=monad) for more packages\n\n### UML\n\n![class diagram](https://github.com/chippyash/Monad/blob/master/docs/monad-classes.png)\n\n## Changing the library\n\n1.  fork it\n2.  write the test\n3.  amend it\n4.  do a pull request\n\nFound a bug you can't figure out?\n\n1.  fork it\n2.  write the test\n3.  do a pull request\n\nNB. Make sure you rebase to HEAD before your pull request\n\nOr - raise an issue ticket.\n\n## Where?\n\nThe library is hosted at [Github](https://github.com/chippyash/Monad). It is\navailable at [Packagist.org](https://packagist.org/packages/chippyash/monad)\n\n### Installation\n\nInstall [Composer](https://getcomposer.org/)\n\n#### For production\n\n\u003cpre\u003e\n    \"chippyash/monad\": \"~3.0\"\n\u003c/pre\u003e\n\nOr to use the latest, possibly unstable version:\n\n\u003cpre\u003e\n    \"chippyash/monad\": \"dev-master\"\n\u003c/pre\u003e\n\n\n#### For development\n\nClone this repo, and then run Composer in local repo root to pull in dependencies\n\n\u003cpre\u003e\n    git clone git@github.com:chippyash/Monad.git Monad\n    cd Monad\n    composer install\n\u003c/pre\u003e\n\nTo run the tests:\n\n\u003cpre\u003e\n    cd Monad\n    vendor/bin/phpunit -c test/phpunit.xml test/\n\u003c/pre\u003e\n\n##### Debugging\n\nBecause PHP doesn't really support functional programming at it's core level, debugging\n  using XDebug etc becomes a nested mess. Some things I've found helpful:\n\n- isolate your tests, at least at the initial stage. If you get a problem, create a test\nthat does one thing - the thing you are trying to debug. Use that as your start point.\n\n- be mindful of value() and flatten(), the former gets the immediate Monad value, the\nlatter gives you a PHP fundamental.\n\n- when constructing FMatches, ensure the value contained in the FMatch conforms to the\ntype you are expecting.  Remember, FMatch returns a FMatch with a value. And yes, I've\ntripped up on this myself.\n\n- keep running the other tests. Seems simple, but in the headlong pursuit of your\nsingle objective, it's easy to forget that the library is interdependent (and will \n become increasingly so as we are able to wrap new functionality back into the original\n code. e.g. Collection is dependent on FMatch: when FFor is implemented, FMatch will change.)\n Run the whole test suite on a regular basis. That way you catch anything that has broken\n upstream functionality.  This library will be complete when it, as far as possible,\n expresses itself in terms of itself!\n \n- the tests that are in place are there for a good reason: open an issue if you think\nthey are wrong headed, misguided etc\n\n## License\n\nThis software library is released under the [BSD 3 Clause license](https://opensource.org/licenses/BSD-3-Clause)\n\nThis software library is Copyright (c) 2015,2021 Ashley Kitson, UK\n\nThis software library contains code items that are derived from other works: \n\nNone of the contained code items breaks the overriding license, or vice versa,  as \nfar as I can tell.  If at all unsure, please seek appropriate advice.\n\nIf the original copyright owners of the derived code items object to this inclusion, please contact the author.\n\n## Thanks\n\nI didn't do this by myself. I'm deeply indebted to those that trod the path before me.\n \nThe following have done work on which this library is based:\n\n[Sean Crystal](https://github.com/spiralout/Phonads)\n\n[Anthony Ferrara](http://blog.ircmaxell.com/2013/07/taking-monads-to-oop-php.html)\n\n[Johannes Schmidt](https://github.com/schmittjoh/php-option)\n\n## History\n\nV1.0.0 Initial Release\n\nV1.1.0 Added FTry\n\nV1.2.0 Added Collection\n\nV1.2.1 fixes on Collection\n\nV1.2.2 add sort order for vUnion method\n\nV1.2.3 allow descendent monadic types\n\nV1.2.4 add each() method to Collection\n\nV1.2.5 move from coveralls to codeclimate\n\nV1.2.6 Add link to packages\n\nV1.2.7 Code cleanup - verify PHP7 compatibility\n\nV1.3.0 Collection is immutable. Added MutableCollection for convenience \n\nV1.4.0 Add Map class - enforced string type keys for collection members\n\n       Add convenience method append() to Collection === -\u003evUnion(new Collection([$nValue]))\n       \nV1.5.0 Add Set class\n\nV1.5.1 Add additional checking for Maps and Sets. diff() and intersect()\ndeprecated, use the kDiff(), uDiff, kIntersect() and uIntersect() methods;\n\nV1.5.2 build script update\n\nV1.5.3 update composer - forced by packagist composer.json format change\n\nV2.0.0 BC Break. Support for PHP \u003c5.6 withdrawn\n\nV2.0.1 fixes for PHP \u003e= 7.1\n\nV2.1.0 Change of license from GPL V3 to BSD 3 Clause\n\nV2.1.1 Flatten value in the bind method of FTry, so in case the binded function \nreturns a Success, we do not end up with nested Success. PR by [josselinauguste](https://github.com/josselinauguste)\n\nV3.0.0 BC Break. Support for PHP \u003c8 withdrawn. Match renamed to FMatch.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchippyash%2Fmonad","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchippyash%2Fmonad","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchippyash%2Fmonad/lists"}