├── .codeclimate.yml
├── .gitignore
├── .travis.yml
├── LICENSE.md
├── README.md
├── build.sh
├── composer.json
├── docs
├── Monad Classes.puml
├── Test-Contract.md
└── monad-classes.png
├── phpunit.travis.xml
├── src
└── chippyash
│ └── Monad
│ ├── CallFunctionAble.php
│ ├── Collection.php
│ ├── FMatch.php
│ ├── FTry.php
│ ├── FTry
│ ├── Failure.php
│ └── Success.php
│ ├── FlattenAble.php
│ ├── Identity.php
│ ├── Map.php
│ ├── Monad.php
│ ├── Monadic.php
│ ├── MutableCollection.php
│ ├── Option.php
│ ├── Option
│ ├── None.php
│ └── Some.php
│ ├── ReturnValueAble.php
│ └── Set.php
└── test
├── .gitignore
├── phpunit.xml
└── src
└── chippyash
├── FTry
├── FailureTest.php
└── SuccessTest.php
└── Monad
├── CollectionTest.php
├── FMatchTest.php
├── FTryTest.php
├── IdentityTest.php
├── MapTest.php
├── MonadTest.php
├── MutableCollectionTest.php
├── Option
├── NoneTest.php
└── SomeTest.php
├── OptionTest.php
└── SetTest.php
/.codeclimate.yml:
--------------------------------------------------------------------------------
1 | # Save as .codeclimate.yml (note leading .) in project root directory
2 | languages:
3 | Ruby: false
4 | JavaScript: false
5 | PHP: true
6 | Python: false
7 | exclude_paths:
8 | - "test/*"
9 | - "docs/*"
10 | - "examples/*"
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | nbproject*
2 | vendor*
3 | composer.lock
4 | .directory
5 | .idea
6 | .phpunit.result.cache
7 | build
8 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | # see http://about.travis-ci.org/docs/user/languages/php/ for more hints
2 | language: php
3 | #possible workaround for https://github.com/composer/composer/issues/6342
4 |
5 | # list any PHP version you want to test against
6 | php:
7 | # aliased to a recent 8.x version
8 | - 8.0
9 | # hhvm
10 | #- hhvm
11 |
12 | # omitting "script:" will default to phpunit
13 | # use the $DB env variable to determine the phpunit.xml to use
14 | before_script:
15 | - composer install --no-interaction
16 | - mkdir -p build/logs
17 | - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
18 | - chmod +x ./cc-test-reporter
19 | - ./cc-test-reporter before-build
20 |
21 | script:
22 | - vendor/phpunit/phpunit/phpunit --configuration ./phpunit.travis.xml test
23 |
24 | after_success:
25 | - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
26 |
27 | # configure notifications (email, IRC, campfire etc)
28 | notifications:
29 | email: "ashley@zf4.biz"
30 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2018 Ashley Kitson, UK
2 |
3 | Redistribution and use in source and binary forms, with or without modification,
4 | are permitted provided that the following conditions are met:
5 |
6 | 1. Redistributions of source code must retain the above copyright notice, this list
7 | of conditions and the following disclaimer.
8 |
9 | 2. Redistributions in binary form must reproduce the above copyright notice, this
10 | list of conditions and the following disclaimer in the documentation and/or other
11 | materials provided with the distribution.
12 |
13 | 3. Neither the name of the copyright holder nor the names of its contributors may be
14 | used to endorse or promote products derived from this software without specific
15 | prior written permission.
16 |
17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
18 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
20 | SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
22 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
23 | BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
25 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
26 | DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # chippyash/Monad
2 |
3 | ## Quality Assurance
4 |
5 | 
6 | [](https://travis-ci.org/chippyash/Monad)
7 | [](https://codeclimate.com/github/chippyash/Monad/coverage)
8 | [](https://codeclimate.com/github/chippyash/Monad)
9 |
10 | The above badges represent the current development branch. As a rule, I don't push
11 | to GitHub unless tests, coverage and usability are acceptable. This may not be
12 | true for short periods of time; on holiday, need code for some other downstream
13 | project etc. If you need stable code, use a tagged version. Read 'Further Documentation'
14 | and 'Installation'.
15 |
16 | [Test Contract](https://github.com/chippyash/Monad/blob/master/docs/Test-Contract.md) in the docs directory.
17 |
18 | Developer support for PHP5.4 & 5.5 was withdrawn at version 2.0.0 of this library.
19 | If you need support for PHP 5.4 or 5.5, please use a version`>=1,<2`
20 |
21 | Developer support for PHP <8 was withdrawn at version 3.0.0 of this library.
22 | If you need support for PHP 7.x, please use a version`>=2,<3`
23 |
24 | ## What?
25 |
26 | Provides a Monadic type
27 |
28 | According to my mentor, Monads are either difficult to explain or difficult to code,
29 | i.e. you can say `how` or `what` but not at the same time. If
30 | you need further illumination, start with [wikipedia](https://en.wikipedia.org/wiki/Monad_(functional_programming))
31 |
32 | ### Types supported
33 |
34 | * Monadic Interface
35 | * Abstract Monad
36 | * Identity Monad
37 | * Option Monad
38 | * Some
39 | * None
40 | * FTry Monad
41 | * Success
42 | * Failure
43 | * FMatch Monad
44 | * Collection Monad
45 | * Map Monad
46 | * Set Monad
47 |
48 | ## Why?
49 |
50 | PHP is coming under increasing attack from functional hybrid languages such as Scala.
51 | The difference is the buzzword of `functional programming`. PHP can support this
52 | paradigm, and this library introduces some basic monadic types. Indeed, learning
53 | functional programming practices can make solutions in PHP far more robust.
54 |
55 | Much of the power of monadic types comes through the use of the functional FMatch,
56 | Try and For Comprehension language constructs. PHP doesn't have these. This library provides:
57 |
58 | - FMatch
59 | - FTry
60 | - FFor This is provided in the [Assembly-Builder package](https://github.com/chippyash/Assembly-Builder)
61 |
62 | Key to functional programming is the use of strict typing and elevating functions as
63 | first class citizens within the language syntax. PHP5.4+ allows functions to be used as
64 | a typed parameter (Closure). It also appears that PHP devs are coming to terms with
65 | strict or hard types as the uptake of my [strong-type library](https://packagist.org/packages/chippyash/strong-type) testifies.
66 |
67 | ## How
68 |
69 | ### The Monadic interface
70 |
71 | A Monad has three things (according to my understanding of it):
72 |
73 | - a value (which may be no value at all, a simple type, an object or a function)
74 | - method of getting its value, often referred to as return()
75 | - a way of binding (or using) the value into some function, often referred to as bind(),
76 | the return value of which is another Monad, often but not always of the same type as
77 | the donor Monad. (Rarely, it could be another class type.)
78 |
79 | The Monadic Interface supplied here defines
80 |
81 | - bind(\Closure $function, array $args = []):Monadic
82 | - The function signature should contain at least one parameter to receive the value
83 | of the Monad. e.g. `function($val){return $val * 2;}` If you are using additional
84 | arguments in the $args array, you'll need to add them to the parameter list e.g.
85 |
86 | $ret = $m->bind(function($val, $mult){return $val * $mult;}, [4]);
87 |
88 | Bear in mind that you can use the `use` clause as normal when defining your function
89 | to expose external parameter values. Caveat: start using this stuff in pure Async
90 | PHP programming and you can't use the `use` clause. You have been warned!
91 |
92 |
93 | - value():mixed - the return() method as `return` is a reserved word in PHP
94 |
95 | Additionally, two helper methods are defined for the interface
96 |
97 | - flatten():mixed - the monadic value `flattened` to a PHP native type or non Monadic object
98 | - static create(mixed $value):Monadic A factory method to create an instance of the concrete descendant Monad
99 |
100 | Monads have an immutable value, that is to say, the result of the bind()
101 | method is another (Monadic) class. The original value is left alone.
102 |
103 | ### The Monad Abstract class
104 |
105 | Contains the Monad value holder and a `syntatic sugar` helper magic \__invoke() method that
106 | proxies to value() if no parameters supplied or bind() if a Closure (with/without optional arguments)
107 | are supplied.
108 |
109 | Neither the Monadic interface or the abstract Monad class define how to set a value on
110 | construction of a concrete Monad. It usually makes sense to set the Monad's value on construction.
111 | Therefore in most circumstances you would create concrete Monad classes with some
112 | form of constructor.
113 |
114 | ### Concrete Monad classes supplied
115 |
116 | #### Identity
117 | The simplest type of Monad
118 |
119 |
120 | use Monad\Identity;
121 |
122 | $id = new Identity('foo');
123 | //or
124 | $id = Identity::create('foo');
125 |
126 | $fConcat = function($value, $fudge){return $value . $fudge};
127 | $concat = $id->bind($fConcat, ['bar'])
128 | ->bind($fConcat, ['baz']);
129 |
130 | echo $concat->value(); //'foobarbaz'
131 | echo $id->value(); //'foo'
132 |
133 |
134 | #### Option
135 |
136 | An Option is a polymorphic `Maybe Monad` that can exist in one of two states:
137 |
138 | - Some - an option with a value
139 | - None - an option with no value
140 |
141 | As PHP does not have the language construct to create a polymorphic object by construction,
142 | you'll need to use the Option::create() static method. You can however use Option as
143 | a type hint for other class methods and returns
144 |
145 |
146 | use Monad\Option;
147 | use Monad\Option\Some;
148 | use Monad\Option\None;
149 |
150 | /**
151 | * @param Option $opt
152 | * @return Option
153 | */
154 | function doSomethingWithAnOption(Option $opt) {
155 | if ($opt instanceof None) {
156 | return $opt;
157 | }
158 |
159 | //must be a Some
160 | return $opt(doMyOtherThing()); //use magic invoke to bind
161 |
162 | }
163 |
164 | $someOption = Option::create('foo');
165 | $noneOption = Option::create();
166 |
167 | $one = doSomethingWithAnOption($someOption);
168 | $two = doSomethingWithAnOption($noneOption);
169 |
170 |
171 | Under normal circumstances, Option uses the `null` value to determine whether or not
172 | to create a Some or a None; that is, the value passed into create() is tested against
173 | `null`. If it === null, then a None is created, else a Some. You can provide an
174 | alternative test value as a second parameter to create()
175 |
176 |
177 | $mySome = Option::create(true, false);
178 | $myNone = Option::create(false, false);
179 |
180 |
181 | Once a None, always a None. No amount of binding will return anything other than a None.
182 | On the other hand, a Some can become a None through binding, (or rather the result of
183 | the bind() as of course the original Some remains immutable.) To assist in this,
184 | Some->bind() can take an optional third parameter, which is the value to test against
185 | for None (i.e. like the optional second parameter to Option::create() )
186 |
187 | You should also note that calling ->value() on a None will generate a RuntimeException
188 | because of course, a None does not have a value!
189 |
190 | ##### Other methods supported
191 |
192 | * getOrElse(mixed:elseValue) If the Option is a Some, return the Option->value() else
193 | return the elseValue
194 |
195 | #### FTry
196 |
197 | An FTry is a polymorphic `Try Monad` that can exist in one of two states:
198 |
199 | - Success - an FTry with a value
200 | - Failure - an FTry with a PHP Exception as a value
201 |
202 | `Try` is a reserved word in PHP, so I have called this class FTry to mean `Functional Try`.
203 |
204 | As PHP does not have the language construct to create a polymorphic object by construction,
205 | you'll need to use the FTry::with() (or FTry::create()) static method. You can however
206 | use FTry as a type hint for other class methods and returns
207 |
208 | FTry::on(value) will catch any Exception incurred in processing the value, and return
209 | a Success or Failure class appropriately. This makes it ideal for the simple case
210 | of wrapping a PHP transaction in a Try - Catch block:
211 |
212 |
213 | use Monad\FTry;
214 | use Monad\FMatch;
215 |
216 | FMatch::on(FTry::with($myFunction($initialValue())))
217 | ->Monad_FTry_Success(function ($v) {doSomethingGood($v);})
218 | ->Monad_FTry_Failure(
219 | function (\Exception $e) {
220 | echo "Exception: " . $e->getMessage();
221 | }
222 | );
223 |
224 |
225 | A fairly simplistic example, and one where you might question its value, as it could have
226 | been written as easily using conventional PHP. But: A Success or Failure is still
227 | a Monad, and so you can still bind (map) onto the resultant class, flatten it etc.
228 |
229 | Like Option, FTry also supports the `getOrElse(mixed:elseValue)` method allowing for implementing
230 | default behaviours:
231 |
232 |
233 | echo FTry::with(myComplexPrintableTransaction())
234 | ->getOrElse('Sorry - that failed');
235 |
236 |
237 | For completeness, FTry also supports `isSuccess()`:
238 |
239 |
240 | echo 'The colour is' . FTry::with(myTest())->isSuccess() ? 'blue' : 'red';
241 |
242 |
243 | Once a Failure, always a Failure. However, A Success can yield either a Success
244 | or a Failure as a result of binding.
245 |
246 | If you really want to throw the exception contained in a Failure use the `pass()` method
247 |
248 |
249 | $try = FTry::with($myFunction());
250 | if (!$try->isSuccess()) $try->pass();
251 |
252 |
253 | #### FMatch
254 |
255 | The FMatch Monad allows you to carry out type pattern matching to create powerful and
256 | dynamic functional equivalents of `case statements`.
257 |
258 | 'Match' is a reserved word since PHP8. Thus for V3 of this library, I've
259 | renamed Match to FMatch.
260 |
261 | The basic syntax is
262 |
263 |
264 | use Monad\FMatch;
265 |
266 | $result = FMatch::on($initialValue)
267 | ->test()
268 | ->test()
269 | ->value();
270 |
271 |
272 | where test() can be the name of a native PHP type or the name of a class, e.g.:
273 |
274 |
275 | $result = FMatch::on($initialValue)
276 | ->string()
277 | ->Monad_Option()
278 | ->Monad_Identity()
279 | ->value()
280 |
281 |
282 | You can use the FMatch::any() method to catch anything not matched by a specific matcher:
283 |
284 |
285 | $result = FMatch::on($initialValue)
286 | ->string()
287 | ->int()
288 | ->any()
289 | ->value();
290 |
291 |
292 | You can provide a concrete value as a parameter to each test, or a function. e.g.
293 |
294 |
295 | $result = FMatch::on($initialValue)
296 | ->string('foo')
297 | ->Monad_Option(
298 | function ($v) {
299 | return FMatch::on($v)
300 | ->Monad_Option_Some(function ($v) {
301 | return $v->value();
302 | })
303 | ->Monad_Option_None(function () {
304 | throw new \Exception();
305 | })
306 | ->value();
307 | }
308 | )
309 | ->Monad_Identity(
310 | function ($v) {
311 | return $v->value() . 'bar';
312 | }
313 | )
314 | ->any(function(){return 'any';})
315 | ->value();
316 |
317 |
318 | You can find this being tested in FMatchTest::testYouCanNestFMatches()
319 |
320 | ##### Supported native type matches
321 |
322 | - string
323 | - integer|int|long
324 | - float|double|real
325 | - null
326 | - array
327 | - bool|boolean
328 | - callable|function|closure
329 | - file
330 | - dir|directory
331 | - object
332 | - scalar
333 | - numeric
334 | - resource
335 |
336 | ##### Supported class matching
337 |
338 | Use the fully namespaced name of the class to match, substituting the backslash \\
339 | with an underscore e.g. to test for `Monad\Option` use `Monad_Option`
340 |
341 | #### Collection
342 |
343 | The Monad Collection provides a structured array that behaves as a Monad. It is based
344 | on the SPL ArrayObject.
345 |
346 | Very important to note however is that unlike a PHP array, the Collection is type
347 | specific, i.e. you specify Collection type specifically or by default as the first member
348 | of its construction array.
349 |
350 | Another 'gotcha': As the Collection is an object, calling Collection->value() will
351 | just return the Collection itself. If you want to get a PHP array from the Collection
352 | then use `toArray()` which proxies the underlying `getArrayCopy()` and is provided
353 | as most PHPers are familiar with `toArray` as being a missing 'magic' call.
354 |
355 | Why re-invent the wheel? ArrayObject (underpinning Collection,) behaves in subtly
356 | different ways than a plain vanilla array. One: it's an object and can therefore
357 | be passed by reference, Two: because of One, it (hopefully TBC,) stops segfaults
358 | occurring in a multi thread environment. Even if Two doesn't pan out, then One still
359 | holds.
360 |
361 |
362 | use Monad\Collection;
363 |
364 | $c = Collection::create([1,2,3,4]);
365 | //or
366 | $c = Collection::create([1,2,3,4], 'integer');
367 |
368 | //to create an empty collection, you must specify type
369 | $c = Collection::create([], 'integer');
370 | $c = Collection::create([], 'Monad\Option');
371 |
372 |
373 | You can get and test a Collection:
374 |
375 |
376 | $c = Collection::create([1,2,3,4]);
377 | $v = $c[2] // == 3
378 |
379 | if (!isset($c[6]) {
380 | ...
381 | }
382 |
383 |
384 | Although the Collection implements the ArrayAccess interface, trying to set or unset
385 | a value `$mCollection[0] = 'foo'` or `unset($mCollection[0])` *will* throw an
386 | exception, as Collections are *immutable* by default. In some circumstances, you
387 | may want to change this. Use the MutableCollection to allow mutability.
388 |
389 | As usual, this is not really a problem, as you can bind() or use each() on a Collection
390 | to return another Collection, (which can contain values of a different type.)
391 | Wherever possible, I've expressed the Collection implementation in terms of FMatch
392 | statements, not only because it usually means tighter code, but as something that
393 | you can look at (and criticise hopefully!) by example.
394 |
395 | You can append to a Collection, returning a new Collection
396 |
397 |
398 | $s1 = new Collection([1,2,3]);
399 | $s2 = $s1->append(4);
400 | //or
401 | $s2 = $s1->append(['foo'=>4]);
402 |
403 |
404 | You can get the difference of two collections:
405 |
406 |
407 | $s1 = Collection::create([1, 2, 3, 6, 7]);
408 | $s2 = Collection::create([6,7]);
409 | $s3 = $s1->vDiff($s2); //difference on values
410 | $s4 = $s1->kDiff($s2); //difference on keys
411 |
412 |
413 | And the intersection:
414 |
415 |
416 | $s1 = Collection::create([1, 2, 3, 6, 7]);
417 | $s2 = Collection::create([6,7]);
418 | $s3 = $s1->vIntersect($s2); //intersect on values
419 | $s4 = $s1->kIntersect($s2); //intersect on keys
420 |
421 |
422 | `uDiff`, `kDiff`, `vIntersect` and `kIntersect` can take a second optional Closure parameter which is used
423 | as the comparator method.
424 |
425 | You can get the union of two collections, either by value or key:
426 |
427 |
428 | $s1 = Collection::create([1, 2, 3, 6, 7]);
429 | $s2 = Collection::create([3, 6, 7, 8]);
430 | $valueUnion = $s1->vUnion($s2);
431 | $keyUnion = $s1->kUnion($s2);
432 |
433 |
434 | You can get the head and the tail of a collection:
435 |
436 |
437 | $s1 = Collection::create([1, 2, 3, 6, 7]);
438 | echo $s1->head()[0] // 1
439 | echo $s1->tail()[0] // 2
440 | echo $s1->tail()[3] // 7
441 |
442 |
443 | There are four function mapping methods for a Collection:
444 |
445 | - the standard Monadic bind(), whose function takes the entire `value array` of the
446 | Collection as its parameter. You should return an array as a result of the function
447 | but in the event that you do not, it will be forced to a Collection.
448 |
449 | - the each() method. Like bind(), this takes a function and an optional array of
450 | additional parameter values to pass on. However, the each function is called for
451 | each member of the collection. The results of the function are collected into a new
452 | Collection and returned. In this way, it behaves rather like the PHP native array_map.
453 |
454 | - the reduce() method. Acts just like array_reduce and returns a single value as a result
455 | of function passed in as a paramter.
456 |
457 | - the filter() method. Acts just like array_filter, but returns a new Collection as a
458 | result of the reduction.
459 |
460 | Note that you can change the base type of a resultant Collection as a result of these
461 | mapping methods().
462 |
463 | I chose Collection as the name as it doesn't clash with `list` which is a PHP reserved name.
464 | In essence, Collection will to all intents and purposes be a List, but for die hard PHPers
465 | still behave as an array.
466 |
467 | A secondary design consideration, is that you should be able to use Collection
468 | oblivious of that fact that it is a Monad, except that it is type specific.
469 |
470 | #### Map
471 |
472 | A Map is a simple extension of a Collection that requires its entries to have a string (hash)
473 | key. It obeys all the rules of a Collection except that
474 |
475 |
476 | use Monad/Map;
477 |
478 | $m1 = new Map(['foo']);
479 |
480 |
481 | will not work, but
482 |
483 |
484 | $m1 = new Map(['foo'=>'bar']);
485 |
486 |
487 | will work. You can as usual, specify the type as a second parameter. The
488 | `vUnion`, `vIntersect` and `vDiff` methods are unspecified for Maps and will throw a
489 | `BadMethodCallException`.
490 |
491 | #### Set
492 |
493 | A Set is a simple extension of Collection that enforces the following rules
494 |
495 | - A Set can only have unique values (of the same type)
496 | - A Set doesn't care about the keys, its the values that are important
497 | - Operations on a Set return a Set
498 |
499 |
500 | use Monad/Set;
501 |
502 | $setA = new Set(['a','b','c']);
503 | $setB = new Set(['a','c']);
504 |
505 | $setC = $setA->vIntersect($setB);
506 | $setD = $setA->vUnion($setB);
507 | $setE = $setA->vDiff($setB);
508 |
509 |
510 | As with a Collection, you can specify an empty construction value and a second type value.
511 | You can also append to a Set to return a new Set.
512 |
513 | The `kUnion`, `kIntersect` and `kDiff` methods are unspecified for Maps and will throw a
514 | `BadMethodCallException`.
515 |
516 | All other Collection methods are supported, returning Sets where expected.
517 |
518 | The ->vIntersect(), ->vUnion() and ->diff() methods all accept a second equality function
519 | parameter as per Collection. However, for Set, if none is provided it will default
520 | to using a sane default, that is to casting non stringifiable values to a serialized
521 | hash of the value and using that for comparison. Supply your own functions if this
522 | default is inadequate for your purposes.
523 |
524 | ## Further documentation
525 |
526 | Please note that what you are seeing of this documentation displayed on Github is
527 | always the latest dev-master. The features it describes may not be in a released version
528 | yet. Please check the documentation of the version you Compose in, or download.
529 |
530 | [Test Contract](https://github.com/chippyash/Monad/blob/master/docs/Test-Contract.md) in the docs directory.
531 |
532 | Check out [ZF4 Packages](http://zf4.biz/packages?utm_source=github&utm_medium=web&utm_campaign=blinks&utm_content=monad) for more packages
533 |
534 | ### UML
535 |
536 | 
537 |
538 | ## Changing the library
539 |
540 | 1. fork it
541 | 2. write the test
542 | 3. amend it
543 | 4. do a pull request
544 |
545 | Found a bug you can't figure out?
546 |
547 | 1. fork it
548 | 2. write the test
549 | 3. do a pull request
550 |
551 | NB. Make sure you rebase to HEAD before your pull request
552 |
553 | Or - raise an issue ticket.
554 |
555 | ## Where?
556 |
557 | The library is hosted at [Github](https://github.com/chippyash/Monad). It is
558 | available at [Packagist.org](https://packagist.org/packages/chippyash/monad)
559 |
560 | ### Installation
561 |
562 | Install [Composer](https://getcomposer.org/)
563 |
564 | #### For production
565 |
566 |
567 | "chippyash/monad": "~3.0"
568 |
569 |
570 | Or to use the latest, possibly unstable version:
571 |
572 |
573 | "chippyash/monad": "dev-master"
574 |
575 |
576 |
577 | #### For development
578 |
579 | Clone this repo, and then run Composer in local repo root to pull in dependencies
580 |
581 |
582 | git clone git@github.com:chippyash/Monad.git Monad
583 | cd Monad
584 | composer install
585 |
586 |
587 | To run the tests:
588 |
589 |
590 | cd Monad
591 | vendor/bin/phpunit -c test/phpunit.xml test/
592 |
593 |
594 | ##### Debugging
595 |
596 | Because PHP doesn't really support functional programming at it's core level, debugging
597 | using XDebug etc becomes a nested mess. Some things I've found helpful:
598 |
599 | - isolate your tests, at least at the initial stage. If you get a problem, create a test
600 | that does one thing - the thing you are trying to debug. Use that as your start point.
601 |
602 | - be mindful of value() and flatten(), the former gets the immediate Monad value, the
603 | latter gives you a PHP fundamental.
604 |
605 | - when constructing FMatches, ensure the value contained in the FMatch conforms to the
606 | type you are expecting. Remember, FMatch returns a FMatch with a value. And yes, I've
607 | tripped up on this myself.
608 |
609 | - keep running the other tests. Seems simple, but in the headlong pursuit of your
610 | single objective, it's easy to forget that the library is interdependent (and will
611 | become increasingly so as we are able to wrap new functionality back into the original
612 | code. e.g. Collection is dependent on FMatch: when FFor is implemented, FMatch will change.)
613 | Run the whole test suite on a regular basis. That way you catch anything that has broken
614 | upstream functionality. This library will be complete when it, as far as possible,
615 | expresses itself in terms of itself!
616 |
617 | - the tests that are in place are there for a good reason: open an issue if you think
618 | they are wrong headed, misguided etc
619 |
620 | ## License
621 |
622 | This software library is released under the [BSD 3 Clause license](https://opensource.org/licenses/BSD-3-Clause)
623 |
624 | This software library is Copyright (c) 2015,2021 Ashley Kitson, UK
625 |
626 | This software library contains code items that are derived from other works:
627 |
628 | None of the contained code items breaks the overriding license, or vice versa, as
629 | far as I can tell. If at all unsure, please seek appropriate advice.
630 |
631 | If the original copyright owners of the derived code items object to this inclusion, please contact the author.
632 |
633 | ## Thanks
634 |
635 | I didn't do this by myself. I'm deeply indebted to those that trod the path before me.
636 |
637 | The following have done work on which this library is based:
638 |
639 | [Sean Crystal](https://github.com/spiralout/Phonads)
640 |
641 | [Anthony Ferrara](http://blog.ircmaxell.com/2013/07/taking-monads-to-oop-php.html)
642 |
643 | [Johannes Schmidt](https://github.com/schmittjoh/php-option)
644 |
645 | ## History
646 |
647 | V1.0.0 Initial Release
648 |
649 | V1.1.0 Added FTry
650 |
651 | V1.2.0 Added Collection
652 |
653 | V1.2.1 fixes on Collection
654 |
655 | V1.2.2 add sort order for vUnion method
656 |
657 | V1.2.3 allow descendent monadic types
658 |
659 | V1.2.4 add each() method to Collection
660 |
661 | V1.2.5 move from coveralls to codeclimate
662 |
663 | V1.2.6 Add link to packages
664 |
665 | V1.2.7 Code cleanup - verify PHP7 compatibility
666 |
667 | V1.3.0 Collection is immutable. Added MutableCollection for convenience
668 |
669 | V1.4.0 Add Map class - enforced string type keys for collection members
670 |
671 | Add convenience method append() to Collection === ->vUnion(new Collection([$nValue]))
672 |
673 | V1.5.0 Add Set class
674 |
675 | V1.5.1 Add additional checking for Maps and Sets. diff() and intersect()
676 | deprecated, use the kDiff(), uDiff, kIntersect() and uIntersect() methods;
677 |
678 | V1.5.2 build script update
679 |
680 | V1.5.3 update composer - forced by packagist composer.json format change
681 |
682 | V2.0.0 BC Break. Support for PHP <5.6 withdrawn
683 |
684 | V2.0.1 fixes for PHP >= 7.1
685 |
686 | V2.1.0 Change of license from GPL V3 to BSD 3 Clause
687 |
688 | V2.1.1 Flatten value in the bind method of FTry, so in case the binded function
689 | returns a Success, we do not end up with nested Success. PR by [josselinauguste](https://github.com/josselinauguste)
690 |
691 | V3.0.0 BC Break. Support for PHP <8 withdrawn. Match renamed to FMatch.
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | cd ~/Projects/chippyash/source/Monad
3 | vendor/phpunit/phpunit/phpunit -c test/phpunit.xml --testdox-html contract.html test/
4 | tdconv -t "Chippyash Monad" contract.html docs/Test-Contract.md
5 | rm contract.html
6 |
7 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chippyash/monad",
3 | "description": "Functional programming Monad support",
4 | "homepage": "http://zf4.biz/packages?utm_source=packagist&utm_medium=web&utm_campaign=blinks&utm_content=monad",
5 | "license": "BSD-3-Clause",
6 | "keywords": [
7 | "monad",
8 | "identity",
9 | "option",
10 | "match",
11 | "functional",
12 | "try",
13 | "collection"
14 | ],
15 | "authors": [
16 | {
17 | "name": "Ashley Kitson",
18 | "email": "ashley@zf4.biz"
19 | }
20 | ],
21 | "require": {
22 | "php": ">=8.0"
23 | },
24 | "require-dev": {
25 | "phpunit/phpunit": "~9.5",
26 | "mikey179/vfsstream": "1.6.*"
27 | },
28 | "autoload": {
29 | "psr-4": {
30 | "Monad\\": "src/chippyash/Monad"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/docs/Monad Classes.puml:
--------------------------------------------------------------------------------
1 | @startuml
2 | title Monad classes
3 |
4 | interface Monadic {
5 | value():mixed
6 | flatten():mixed
7 | bind(Closure:function, array:args = []):monadic
8 | {static} create(mixed:value):monadic
9 | }
10 |
11 | abstract class Monad {
12 | #mixed:value
13 | __invoke():mixed
14 | }
15 |
16 | class Identity {
17 | __construct(mixed:value)
18 | }
19 |
20 | abstract class Option {
21 | {static} create(mixed:value, mixed:noneValue=null):Some|None
22 | getOrElse(mixed:elseValue):mixed
23 | }
24 |
25 | class None {
26 | __construct(mixed:value = null)
27 | value():RuntimeException
28 | bind(Closure:function, array:args = []):None
29 | {static} create(mixed:value):None
30 | }
31 |
32 | class Some {
33 | __construct(mixed:value)
34 | bind(Closure:function, array:args = [], mixed:noneValue=null):Some|None
35 | }
36 |
37 | class FMatch {
38 | #mixed:value
39 | #bool:isMatched
40 | __construct(mixed:value, bool:isMatched)
41 | {static} on(mixed:value):Match
42 | __call(string:method, array:args=[]):Match
43 | any(Closure:function = null, array:$args = []):Match
44 | }
45 |
46 | abstract class FTry {
47 | {static} with(mixed:value):Success|Failure
48 | getOrElse(mixed:elseValue):mixed
49 | {abstract} isSuccess():bool
50 | }
51 |
52 | class Success {
53 | __construct(mixed:value)
54 | bind(Closure:function, array:args = []):Success|Failure
55 | isSuccess():bool(true)
56 | }
57 |
58 | class Failure {
59 | __construct(Exception:value)
60 | create(mixed:value = null):Failure
61 | bind(Closure:function, array:args = []):Failure
62 | pass():!!Exception
63 | isSuccess():bool(false)
64 | }
65 |
66 | class Collection <> {
67 | __construct(array:value = [], string:type = null)
68 | diff(Collection:other, Closure:function = null):Collection
69 | intersect(Collection:other, Closure:function = null):Collection
70 | vUnion(Collection:other):Collection
71 | kUnion(Collection:other):Collection
72 | head():Collection
73 | tail():Collection
74 | each():Collection
75 | }
76 |
77 | Monadic <-- Monad
78 | Monadic <-- FMatch
79 | Monadic <-- Collection
80 | Monad <-- Identity
81 | Monad <-- Option
82 | Option <-- None
83 | Option <-- Some
84 | Monad <-- FTry
85 | FTry <-- Success
86 | FTry <-- Failure
87 |
88 | @enduml
--------------------------------------------------------------------------------
/docs/Test-Contract.md:
--------------------------------------------------------------------------------
1 | # Chippyash Monad
2 |
3 | ## Monad\test\Failure
4 |
5 | * ✓ Can construct if value is exception
6 | * ✓ Create creates a failure if value is exception
7 | * ✓ Create creates a failure if value is not an exception
8 | * ✓ Bind returns failure with same value
9 | * ✓ Calling pass will throw an exception
10 | * ✓ Calling is success will return false
11 |
12 | ## Monad\test\Success
13 |
14 | * ✓ You can construct a success if you have a value for it
15 | * ✓ You cannot construct a success with an exception
16 | * ✓ Binding a success with something that does not throw an exception will return success
17 | * ✓ Binding a success with something that throws an exception will return a failure
18 | * ✓ You can get a value from a success
19 | * ✓ Calling is success will return true
20 |
21 | ## Monad\Test\Collection
22 |
23 | * ✓ You can construct a collection with a non empty array
24 | * ✓ You cannot construct a collection with null values
25 | * ✓ You cannot construct a collection with an empty array and no type specified
26 | * ✓ You can construct an empty collection if you pass a type
27 | * ✓ When constructing a collection you must have same type values
28 | * ✓ Constructing a collection with dissimilar types will cause an exception
29 | * ✓ You can create a collection
30 | * ✓ The value of a collection is the collection
31 | * ✓ You can bind a function to the entire collection and return a collection
32 | * ✓ You can bind a function to each member of the collection and return a collection
33 | * ✓ You can count the items in the collection
34 | * ✓ You can get an iterator for a collection
35 | * ✓ You cannot unset a collection member
36 | * ✓ You cannot set a collection member
37 | * ✓ You can get a collection member as an array offset
38 | * ✓ You can test if a collection member exists as an array offset
39 | * ✓ You can create a collection of collections
40 | * ✓ Flattening a collection of collections will return a collection
41 | * ✓ You can get the difference of values between two collections
42 | * ✓ You can get the difference of keys between two collections
43 | * ✓ You can chain v diff methods to act on arbitrary numbers of collections
44 | * ✓ You can chain k diff methods to act on arbitrary numbers of collections
45 | * ✓ You can supply an optional comparator function to v diff method
46 | * ✓ You can supply an optional comparator function to k diff method
47 | * ✓ You can get the intersection of two collections by value
48 | * ✓ You can get the intersection of two collections by key
49 | * ✓ You can chain value intersect methods to act on arbitrary numbers of collections
50 | * ✓ You can chain key intersect methods to act on arbitrary numbers of collections
51 | * ✓ You can supply an optional comparator function to the value intersect method
52 | * ✓ You can supply an optional comparator function to the key intersect method
53 | * ✓ You can get the union of values of two collections
54 | * ✓ You can chain the union of values of two collections
55 | * ✓ You can get the union of keys of two collections
56 | * ✓ You can chain the union of keys of two collections
57 | * ✓ Performing a value union with dissimilar collections will throw an exception
58 | * ✓ Performing a key union with dissimilar collections will throw an exception
59 | * ✓ The head of a collection is its first member
60 | * ✓ The tail of a collection is all but its first member
61 | * ✓ You can filter a collection with a closure
62 | * ✓ You can reduce a collection to a single value with a closure
63 | * ✓ You can reference a collection as though it was an array
64 | * ✓ Value method proxies to collection get array copy method
65 | * ✓ You can flip a collection
66 | * ✓ Appending to a collection returns a new collection
67 |
68 | ## Monad\Test\FTry
69 |
70 | * ✓ Creating an f try with a non exception will return a success
71 | * ✓ Creating an f try with an exception will return a failure
72 | * ✓ The with method proxies to create
73 | * ✓ Get or else will return f try value if option is a success
74 | * ✓ Get or else will return else value if f try is a failure
75 |
76 | ## Monad\Test\Identity
77 |
78 | * ✓ You can create an identity statically
79 | * ✓ Creating an identity with an identity parameter will return the parameter
80 | * ✓ Creating an identity with a non identity parameter will return an identity containing the parameter as value
81 | * ✓ You can bind a function on an identity
82 | * ✓ Bind can take optional additional parameters
83 | * ✓ You can chain bind methods together
84 | * ✓ Binding on an identity with a closure value will evaluate the value
85 | * ✓ You can flatten an identity value to its base type
86 |
87 | ## Monad\Test\Map
88 |
89 | * ✓ You cannot create an empty map
90 | * ✓ Maps require string keys
91 | * ✓ You can construct an empty map if you pass a type
92 | * ✓ Appending to a map returns a new map
93 | * ✓ Appending to a map with unhashed values throws an exception
94 | * ✓ Vunion method is not supported for maps
95 | * ✓ Vintersect method is not supported for maps
96 | * ✓ Vdiff method is not supported for maps
97 |
98 | ## Monad\Test\FMatch
99 |
100 | * ✓ Construction requires a value to match against
101 | * ✓ You can construct via static on factory method
102 | * ✓ You can match on native php types
103 | * ✓ Matching will return set by callable parameter if matched
104 | * ✓ Matching will return set by non callable parameter if matched
105 | * ✓ Failing to match will return new match object with same value as original
106 | * ✓ You can match on a class name
107 | * ✓ Failing to match on class name will return new match object with same value as original
108 | * ✓ You can chain match tests
109 | * ✓ You can chain match tests and bind a function on successful match
110 | * ✓ Binding a match will return a match
111 | * ✓ Match on any method will match anything
112 | * ✓ Match on any method can accept optional function and arguments
113 | * ✓ You can nest matches
114 | * ✓ You can test for equality
115 |
116 | ## Monad\Test\Monad
117 |
118 | * ✓ You can return a value when monad created with simple value
119 | * ✓ You can return a value when monad created with monadic value
120 | * ✓ You can use a closure for value
121 | * ✓ Flatten will return base type
122 | * ✓ You can bind a closure on a monad to create a new monad of the same type
123 | * ✓ Bind can take optional additional parameters
124 | * ✓ Magic invoke proxies to bind method if passed a closure
125 | * ✓ Magic invoke proxies to value method if passed no parameters
126 | * ✓ Calling magic invoke will throw exception if no method is executable
127 | * ✓ You cannot create an abstract monad statically
128 |
129 | ## Monad\Test\MutableCollection
130 |
131 | * ✓ You can unset a mutable collection member
132 | * ✓ You can set a mutable collection member
133 | * ✓ Calling one of the underlaying collection methods returns a mutable collection
134 |
135 | ## Monad\Test\None
136 |
137 | * ✓ You can construct a none
138 | * ✓ You can construct a none with a parameter and it will still be none
139 | * ✓ Create will return a none
140 | * ✓ Binding a none returns a none
141 | * ✓ Calling get on a none throws a runtime exception
142 |
143 | ## Monad\Test\Some
144 |
145 | * ✓ You can construct a some if you have a value for it
146 | * ✓ You cannot construct a some with no value
147 | * ✓ You can get a value from a some
148 | * ✓ Binding on a some may return a some or a none
149 | * ✓ Binding on a some takes a third nonetest value
150 |
151 | ## Monad\Test\Option
152 |
153 | * ✓ You cannot construct an option directly
154 | * ✓ Creating with a value returns a some
155 | * ✓ Creating with no value or null returns a none
156 | * ✓ You can replace none test by calling create with additional parameter
157 | * ✓ Get or else will return option value if option is a some
158 | * ✓ Get or else will return else value if option is a none
159 |
160 | ## Monad\Test\Set
161 |
162 | * ✓ Creating an empty set with no type hint will throw an exception
163 | * ✓ Passing in unique values at construction will create a set
164 | * ✓ Passing in non unique values at construction will create a set with unique values
165 | * ✓ You can create sets of objects
166 | * ✓ You can create sets of resources
167 | * ✓ Value intersection will produce a set
168 | * ✓ Value union will produce a set
169 | * ✓ Diff will produce a set
170 | * ✓ You can bind a function to the entire set and return a set
171 | * ✓ Kintersect method is not supported for sets
172 | * ✓ Kunion method is not supported for sets
173 | * ✓ Kdiff method is not supported for sets
174 |
175 |
176 | Generated by [chippyash/testdox-converter](https://github.com/chippyash/Testdox-Converter)
--------------------------------------------------------------------------------
/docs/monad-classes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chippyash/Monad/5954e8f4403150377b393925a73979821456afb2/docs/monad-classes.png
--------------------------------------------------------------------------------
/phpunit.travis.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
10 |
11 |
12 |
13 | ./src
14 |
15 |
16 | ./docs
17 | ./examples
18 | ./test
19 | ./vendor
20 | ./src/chippyash/Monad/Interfaces
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ./test/src/
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/CallFunctionAble.php:
--------------------------------------------------------------------------------
1 | bind($function, $args);
30 | }
31 |
32 | $val = ($value instanceof \Closure ? $value() : $value);
33 | array_unshift($args, $val);
34 |
35 | return call_user_func_array($function, $args);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/Collection.php:
--------------------------------------------------------------------------------
1 | string(function () use ($type) {
45 | return $this->setType($type);
46 | })
47 | ->null(function () use ($value) {
48 | return $this->setTypeFromValue($value);
49 |
50 | });
51 |
52 | /** @noinspection PhpUndefinedMethodInspection */
53 | parent::__construct(
54 | FMatch::on($setType->value())
55 | ->Monad_FTry_Success(function () use ($value) {
56 | return $this->setValue($value);
57 | })
58 | ->Monad_FTry_Failure(function () use ($setType) {
59 | return $setType->value();
60 | })
61 | ->value()
62 | ->pass()
63 | ->value()
64 | );
65 | }
66 |
67 | /**
68 | * Monadic Interface
69 | *
70 | * @param array $value
71 | *
72 | * @return Collection
73 | */
74 | public static function create($value): Collection
75 | {
76 | return new static($value);
77 | }
78 |
79 | /**
80 | * Bind monad with function. Function is in form f($value){}
81 | * You can pass additional parameters in the $args array in which case your
82 | * function should be in the form f($value, $arg1, ..., $argN)
83 | *
84 | * @param \Closure $function
85 | * @param array $args additional arguments to pass to function
86 | *
87 | * @return Collection
88 | */
89 | public function bind(\Closure $function, array $args = [])
90 | {
91 | $res = $this->callFunction($function, $this, $args);
92 |
93 | return ($res instanceof Collection ? $res : new static(is_array($res)? $res :[$res]));
94 | }
95 |
96 | /**
97 | * For each item in the collection apply the function and return a new collection
98 | *
99 | * @param callable|\Closure $function
100 | * @param array $args
101 | *
102 | * @return Collection
103 | */
104 | public function each(\Closure $function, array $args = []): Collection
105 | {
106 | $content = $this->getArrayCopy();
107 |
108 | return new static(
109 | \array_combine(
110 | \array_keys($content),
111 | \array_map(
112 | function ($value) use ($function, $args) {
113 | return $this->callFunction($function, $value, $args);
114 | },
115 | \array_values($content)
116 | )
117 | )
118 | );
119 | }
120 |
121 | /**
122 | * Reduce the collection using closure to a single value
123 | *
124 | * @see array_reduce
125 | *
126 | * @param \Closure $function
127 | * @param mixed $initial optional initial value
128 | *
129 | * @return mixed
130 | */
131 | public function reduce(\Closure $function, $initial = null)
132 | {
133 | return \array_reduce($this->getArrayCopy(), $function, $initial);
134 | }
135 |
136 | /**
137 | * Filter collection using closure to return another Collection
138 | *
139 | * @see array_filter
140 | *
141 | * @param \Closure $function
142 | *
143 | * @return Collection
144 | */
145 | public function filter(\Closure $function): Collection
146 | {
147 | return new static(\array_filter($this->getArrayCopy(), $function));
148 | }
149 |
150 | /**
151 | * Flip keys and values.
152 | * Returns new Collection
153 | *
154 | * @return Collection
155 | */
156 | public function flip(): Collection
157 | {
158 | return new static(\array_flip($this->getArrayCopy()));
159 | }
160 |
161 | /**
162 | * Monadic Interface
163 | * Return this collection as value
164 | *
165 | * @return Collection
166 | */
167 | public function value(): Collection
168 | {
169 | return $this;
170 | }
171 |
172 | /**
173 | * Return value of Monad as a base type.
174 | *
175 | * If value === \Closure, will evaluate the function and return it's value
176 | * If value === \Monadic, will recurse
177 | *
178 | * @return Collection
179 | */
180 | public function flatten(): Collection
181 | {
182 | $ret = [];
183 | foreach ($this as $key => $value) {
184 | $ret[$key] = FMatch::on($value)
185 | ->Closure(function ($v) {
186 | return $v();
187 | })
188 | ->Monad_Monadic(function ($v) {
189 | return $v->flatten();
190 | })
191 | ->any()
192 | ->flatten();
193 | }
194 |
195 | return new static($ret);
196 | }
197 |
198 | /**
199 | * Return collection as an array
200 | * @return array
201 | */
202 | public function toArray(): array
203 | {
204 | return $this->getArrayCopy();
205 | }
206 |
207 | /**
208 | * ArrayAccess Interface
209 | *
210 | * @param mixed $offset
211 | * @param mixed $value
212 | *
213 | * @throw \BadMethodCallException
214 | */
215 | public function offsetSet($offset, $value)
216 | {
217 | throw new \BadMethodCallException('Cannot set on an immutable Collection');
218 | }
219 |
220 | /**
221 | * ArrayAccess Interface
222 | *
223 | * @param mixed $offset
224 | *
225 | * @throw \BadMethodCallException
226 | */
227 | public function offsetUnset($offset)
228 | {
229 | throw new \BadMethodCallException('Cannot unset an immutable Collection value');
230 | }
231 |
232 | /**
233 | * @deprecated Use vDiff
234 | *
235 | * @param Collection $other
236 | * @param \Closure $function optional function to compare values
237 | *
238 | * @return Collection
239 | */
240 | public function diff(Collection $other, \Closure $function = null): Collection
241 | {
242 | return $this->vDiff($other, $function);
243 | }
244 |
245 | /**
246 | * Compares this collection against another collection using its values for
247 | * comparison and returns a new Collection with the values in this collection
248 | * that are not present in the other collection.
249 | *
250 | * Note that keys are preserved
251 | *
252 | * If the optional comparison function is supplied it must have signature
253 | * function(mixed $a, mixed $b){}. The comparison function must return an integer
254 | * less than, equal to, or greater than zero if the first argument is considered
255 | * to be respectively less than, equal to, or greater than the second.
256 | *
257 | * @param Collection $other
258 | * @param \Closure $function optional function to compare values
259 | *
260 | * @return Collection
261 | */
262 | public function vDiff(Collection $other, \Closure $function = null)
263 | {
264 | if (is_null($function)) {
265 | return new static(\array_diff($this->getArrayCopy(), $other->getArrayCopy()), $this->type);
266 | }
267 |
268 | return new static(\array_udiff($this->getArrayCopy(), $other->getArrayCopy(), $function), $this->type);
269 | }
270 |
271 | /**
272 | * Compares this collection against another collection using its keys for
273 | * comparison and returns a new Collection with the values in this collection
274 | * that are not present in the other collection.
275 | *
276 | * Note that keys are preserved
277 | *
278 | * If the optional comparison function is supplied it must have signature
279 | * function(mixed $a, mixed $b){}. The comparison function must return an integer
280 | * less than, equal to, or greater than zero if the first argument is considered
281 | * to be respectively less than, equal to, or greater than the second.
282 | *
283 | * @param Collection $other
284 | * @param \Closure $function optional function to compare values
285 | *
286 | * @return Collection
287 | */
288 | public function kDiff(Collection $other, \Closure $function = null)
289 | {
290 | if (is_null($function)) {
291 | return new static(\array_diff_key($this->getArrayCopy(), $other->getArrayCopy()), $this->type);
292 | }
293 |
294 | return new static(\array_diff_ukey($this->getArrayCopy(), $other->getArrayCopy(), $function), $this->type);
295 | }
296 |
297 |
298 | /**
299 | * @deprecated - use vIntersect
300 | *
301 | * @param Collection $other
302 | * @param \Closure $function
303 | * @return Collection
304 | */
305 | public function intersect(Collection $other, \Closure $function = null)
306 | {
307 | return $this->vIntersect($other, $function);
308 | }
309 |
310 | /**
311 | * Returns a Collection containing all the values of this Collection that are present
312 | * in the other Collection. Note that keys are preserved
313 | *
314 | * If the optional comparison function is supplied it must have signature
315 | * function(mixed $a, mixed $b){}. The comparison function must return an integer
316 | * less than, equal to, or greater than zero if the first argument is considered
317 | * to be respectively less than, equal to, or greater than the second.
318 | *
319 | * @param Collection $other
320 | * @param callable|\Closure $function Optional function to compare values
321 | *
322 | * @return Collection
323 | */
324 | public function vIntersect(Collection $other, \Closure $function = null)
325 | {
326 | if (is_null($function)) {
327 | return new static(\array_intersect($this->getArrayCopy(), $other->getArrayCopy()), $this->type);
328 | }
329 |
330 | return new static(\array_uintersect($this->getArrayCopy(), $other->getArrayCopy(), $function), $this->type);
331 | }
332 |
333 | /**
334 | * Returns a Collection containing all the values of this Collection that are present
335 | * in the other Collection. Keys are used for comparison
336 | *
337 | * If the optional comparison function is supplied it must have signature
338 | * function(mixed $a, mixed $b){}. The comparison function must return an integer
339 | * less than, equal to, or greater than zero if the first argument is considered
340 | * to be respectively less than, equal to, or greater than the second.
341 | *
342 | * @param Collection $other
343 | * @param \Closure $function Optional function to compare values
344 | *
345 | * @return Collection
346 | */
347 | public function kIntersect(Collection $other, \Closure $function = null)
348 | {
349 | return new static(
350 | FMatch::on(Option::create($function))
351 | ->Monad_Option_Some(function () use ($other, $function) {
352 | return \array_intersect_ukey($this->getArrayCopy(), $other->getArrayCopy(), $function);
353 | })
354 | ->Monad_Option_None(function () use ($other) {
355 | return \array_intersect_key($this->getArrayCopy(), $other->getArrayCopy());
356 | })
357 | ->value(),
358 | $this->type
359 | );
360 | }
361 |
362 | /**
363 | * Return a Collection that is the union of the values of this Collection
364 | * and the other Collection. Note that keys may be discarded and new ones set
365 | *
366 | * @param Collection $other
367 | * @param int $sortOrder arrayUnique sort order. one of SORT_...
368 | *
369 | * @return Collection
370 | */
371 | public function vUnion(Collection $other, $sortOrder = SORT_REGULAR)
372 | {
373 | return new static(
374 | \array_unique(
375 | \array_merge($this->getArrayCopy(), $other->getArrayCopy()),
376 | $sortOrder
377 | )
378 | , $this->type
379 | );
380 | }
381 |
382 | /**
383 | * Return a Collection that is the union of the values of this Collection
384 | * and the other Collection using the keys for comparison
385 | *
386 | * @param Collection $other
387 | *
388 | * @return Collection
389 | */
390 | public function kUnion(Collection $other)
391 | {
392 | return new static($this->getArrayCopy() + $other->getArrayCopy(), $this->type);
393 | }
394 |
395 | /**
396 | * Return a Collection with the first element of this Collection as its only
397 | * member
398 | *
399 | * @return Collection
400 | */
401 | public function head(): Collection
402 | {
403 | return new static(array_slice($this->getArrayCopy(), 0, 1));
404 | }
405 |
406 | /**
407 | * Return a Collection with all but the first member of this Collection
408 | *
409 | * @return Collection
410 | */
411 | public function tail(): Collection
412 | {
413 | return new static(array_slice($this->getArrayCopy(), 1));
414 | }
415 |
416 | /**
417 | * Append value and return a new collection
418 | * NB this uses vUnion
419 | *
420 | * Value will be forced into an array if not already one
421 | *
422 | * @param mixed $value
423 | *
424 | * @return Collection
425 | */
426 | public function append($value)
427 | {
428 | $nValue = (is_array($value) ? $value : [$value]);
429 | return $this->vUnion(new static($nValue));
430 | }
431 |
432 | /**
433 | * @param string $type
434 | *
435 | * @return FTry
436 | */
437 | protected function setType($type): FTry
438 | {
439 | return FMatch::on($type)
440 | ->string(function ($type) {
441 | $this->type = $type;
442 | return FTry::with($type);
443 | })
444 | ->any(function () {
445 | return FTry::with(function () {
446 | return new \RuntimeException('Type must be specified by string');
447 | });
448 | })
449 | ->value();
450 | }
451 |
452 | /**
453 | * @param array $value
454 | *
455 | * @return FTry
456 | */
457 | protected function setTypeFromValue(array $value): FTry
458 | {
459 | //required to be defined as a var so it can be called in next statement
460 | $basicTest = function () use ($value) {
461 | if (count($value) > 0) {
462 | return array_values($value)[0];
463 | }
464 |
465 | return null;
466 | };
467 |
468 | //@var Option
469 | //firstValue is used twice below
470 | $firstValue = Option::create($basicTest());
471 |
472 | //@var Option
473 | //NB - this separate declaration is not needed, but is provided only to
474 | // allow some separation between what can become a complex match pattern
475 | $type = FMatch::on($firstValue)
476 | ->Monad_Option_Some(
477 | function ($option) {
478 | return Option::create(gettype($option->value()));
479 | }
480 | )
481 | ->Monad_Option_None(function () {
482 | return new None();
483 | })
484 | ->value();
485 |
486 | //@var Option
487 | //MatchLegalType requires to be defined separately as it is used twice
488 | //in the next statement
489 | $matchLegalType = FTry::with(
490 | FMatch::on($type)
491 | ->Monad_Option_None()
492 | ->Monad_Option_Some(
493 | function ($v) use ($firstValue) {
494 | FMatch::on($v->value())
495 | ->test('object', function ($v) use ($firstValue) {
496 | $this->setType(get_class($firstValue->value()));
497 | return new Some($v);
498 | })
499 | ->test('string', function ($v) {
500 | $this->setType($v);
501 | return new Some($v);
502 | })
503 | ->test('integer', function ($v) {
504 | $this->setType($v);
505 | return new Some($v);
506 | })
507 | ->test('double', function ($v) {
508 | $this->setType($v);
509 | return new Some($v);
510 | })
511 | ->test('boolean', function ($v) {
512 | $this->setType($v);
513 | return new Some($v);
514 | })
515 | ->test('resource', function ($v) {
516 | $this->setType($v);
517 | return new Some($v);
518 | })
519 | ->any(function () {
520 | return new None();
521 |
522 | });
523 | }
524 | )
525 | ->any(function () {
526 | return new None();
527 | })
528 | );
529 |
530 | return FTry::with(function () use ($matchLegalType) {
531 | return $matchLegalType->value();
532 | });
533 | }
534 |
535 | /**
536 | * @param array $values
537 | *
538 | * @return FTry
539 | */
540 | protected function setValue(array $values): FTry
541 | {
542 | foreach ($values as $key => $value) {
543 | if (($this->type !== gettype($value)) && (!$value instanceof $this->type)) {
544 | return new Failure(new \RuntimeException("Value {$key} is not a {$this->type}"));
545 | }
546 | }
547 |
548 | return new Success($values);
549 | }
550 |
551 | /**
552 | * Call function on value
553 | *
554 | * @param \Closure $function
555 | * @param mixed $value
556 | * @param array $args additional arguments to pass to function
557 | *
558 | * @return mixed
559 | */
560 | protected function callFunction(\Closure $function, $value, array $args = [])
561 | {
562 | if ($value instanceof Monadic && !$value instanceof Collection) {
563 | return $value->bind($function, $args);
564 | }
565 |
566 | $val = ($value instanceof \Closure ? $value() : $value);
567 | array_unshift($args, $val);
568 |
569 | return call_user_func_array($function, $args);
570 | }
571 | }
572 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/FMatch.php:
--------------------------------------------------------------------------------
1 | value = $value;
30 | $this->isMatched = $isMatched;
31 | }
32 |
33 | /**
34 | * Syntactic proxy for create()
35 | * @param mixed $value
36 | *
37 | * @return FMatch
38 | *@see create()
39 | *
40 | */
41 | public static function on($value): FMatch
42 | {
43 | return static::create($value);
44 | }
45 |
46 | /**
47 | * Magic unknown method that proxies native type and class type matching
48 | *
49 | * @param string $method
50 | * @param array $args If args[0] set, then use as concrete value or function to
51 | * bind onto current value
52 | *
53 | * @return FMatch
54 | */
55 | public function __call($method, $args): FMatch
56 | {
57 | if ($this->isMatched) {
58 | return new static($this->value, $this->isMatched);
59 | }
60 |
61 | if ($this->matchOnNative($method) || $this->matchOnClassName($method)) {
62 | if (isset($args[0])) {
63 | if (is_callable($args[0]) && !$args[0] instanceof Monadic) {
64 | return new static($args[0]($this->value), true);
65 | }
66 |
67 | return new static($args[0], true);
68 | }
69 |
70 | return new static($this->value, true);
71 | }
72 |
73 | return new static($this->value);
74 | }
75 |
76 | /**
77 | * Match anything. Usually called last in Match chain
78 | *
79 | * @param callable|\Closure $function
80 | * @param array $args Optional additional arguments to function
81 | * @return FMatch
82 | */
83 | public function any(\Closure $function = null, array $args = []): FMatch
84 | {
85 | if ($this->isMatched) {
86 | return new static($this->value, $this->isMatched);
87 | }
88 |
89 | if (is_null($function)) {
90 | return new static($this->value, true);
91 | }
92 |
93 | return new static($this->callFunction($function, $this->value, $args), true);
94 | }
95 |
96 | /**
97 | * Test current value for exact equality to the test value
98 | *
99 | * @param mixed $test Value to test against
100 | *
101 | * @param \Closure $function Function that is used if test is true
102 | * @param array $args Optional additional arguments to function
103 | *
104 | * @return FMatch
105 | */
106 | public function test($test, \Closure $function = null, array $args = []): FMatch
107 | {
108 | if ($this->isMatched) {
109 | return new static($this->value, $this->isMatched);
110 | }
111 | if ($this->value === $test) {
112 | if (is_null($function)) {
113 | return new static($this->value, true);
114 | }
115 |
116 | return new static($this->callFunction($function, $this->value, $args), true);
117 | }
118 |
119 | return new static($this->value());
120 | }
121 |
122 |
123 | /**
124 | * Return value of Monad
125 | * Does not manipulate the value in any way
126 | *
127 | * @return mixed
128 | */
129 | public function value()
130 | {
131 | return $this->value;
132 | }
133 |
134 | /**
135 | * Bind match value with function.
136 | *
137 | * Function is in form f($value) {}
138 | *
139 | * You can pass additional parameters in the $args array in which case your
140 | * function should be in the form f($value, $arg1, ..., $argN) {}
141 | *
142 | * @param \Closure $function
143 | * @param array $args additional arguments to pass to function
144 | *
145 | * @return FMatch
146 | */
147 | public function bind(\Closure $function, array $args = []): FMatch
148 | {
149 | return new static($this->callFunction($function, $this->value, $args), $this->isMatched);
150 | }
151 |
152 | /**
153 | * Static factory creator for the Monad
154 | *
155 | * @param mixed $value
156 | *
157 | * @return FMatch
158 | */
159 | public static function create($value): FMatch
160 | {
161 | return new static($value);
162 | }
163 |
164 | /**
165 | * @param $name
166 | * @return bool
167 | */
168 | protected function matchOnNative($name): bool
169 | {
170 | switch(strtolower($name)) {
171 | case 'string':
172 | return is_string($this->value);
173 | case 'integer':
174 | case 'int':
175 | case 'long':
176 | return is_int($this->value);
177 | case 'float':
178 | case 'double':
179 | case 'real':
180 | return is_double($this->value);
181 | case 'null':
182 | return is_null($this->value);
183 | case 'array':
184 | return is_array($this->value);
185 | case 'boolean':
186 | case 'bool':
187 | return is_bool($this->value);
188 | case 'callable':
189 | case 'function':
190 | case 'closure':
191 | return is_callable($this->value);
192 | case 'file':
193 | return is_file($this->value);
194 | case 'dir':
195 | case 'directory':
196 | return is_dir($this->value);
197 | case 'object':
198 | return is_object($this->value);
199 | case 'scalar':
200 | return is_scalar($this->value);
201 | case 'numeric':
202 | return is_numeric($this->value);
203 | case 'resource':
204 | return is_resource($this->value);
205 | default:
206 | return false;
207 | }
208 | }
209 |
210 | /**
211 | * @param $name
212 | * @return bool
213 | */
214 | protected function matchOnClassName($name): bool
215 | {
216 | $className = str_replace('_', '\\', $name);
217 |
218 | if (!class_exists($className)) {
219 | return false;
220 | }
221 |
222 | if ($this->value instanceof $className) {
223 | return true;
224 | }
225 |
226 | return false;
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/FTry.php:
--------------------------------------------------------------------------------
1 | flatten());
41 | }
42 |
43 | return new Success($value);
44 |
45 | } catch (\Exception $e) {
46 | return new Failure($e);
47 | }
48 | }
49 |
50 | /**
51 | * Proxy to create()
52 | *
53 | * @param mixed $value
54 | * @return Failure|Success
55 | */
56 | public static function with($value): FTry
57 | {
58 | return static::create($value);
59 | }
60 |
61 | /**
62 | * Return FTry value if Success else the elseValue
63 | *
64 | * @param mixed $elseValue
65 | *
66 | * @return mixed
67 | */
68 | public function getOrElse($elseValue)
69 | {
70 | if ($this instanceof Success) {
71 | return $this->value();
72 | }
73 |
74 | return $elseValue;
75 | }
76 |
77 | /**
78 | * Is this option a Success?
79 | *
80 | * @return bool
81 | */
82 | abstract public function isSuccess(): bool;
83 | }
84 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/FTry/Failure.php:
--------------------------------------------------------------------------------
1 | value = $value;
23 | }
24 |
25 | /**
26 | * Always return another instance of Failure
27 | *
28 | * @param \Exception $value Ignored
29 | *
30 | * @return Failure
31 | */
32 | public static function create($value = null): Failure
33 | {
34 | if ($value instanceof \Exception) {
35 | return new static($value);
36 | }
37 |
38 | return new static(new \RuntimeException('Creating Failure with no exception'));
39 | }
40 |
41 | /**
42 | * Always return another instance of failure
43 | *
44 | * @param \Closure $function Ignored
45 | * @param array $args Ignored
46 | *
47 | * @return Failure
48 | */
49 | public function bind(\Closure $function, array $args = []): Failure
50 | {
51 | return new static($this->value);
52 | }
53 |
54 | /**
55 | * Throw this failure as a php exception
56 | * We use 'pass' as `throw` is a PHP reserved word and I'm a Rugby player
57 | *
58 | * @throw \Exception
59 | */
60 | public function pass()
61 | {
62 | throw $this->value;
63 | }
64 |
65 | /**
66 | * Is this option a Success?
67 | *
68 | * @return bool
69 | */
70 | public function isSuccess(): bool
71 | {
72 | return false;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/FTry/Success.php:
--------------------------------------------------------------------------------
1 | value = $value;
28 | }
29 |
30 | /**
31 | * Return Success or Failure as a result of bind
32 | *
33 | * @param \Closure $function Ignored
34 | * @param array $args Ignored
35 | *
36 | * @return Success|Failure
37 | */
38 | public function bind(\Closure $function, array $args = []): FTry
39 | {
40 | try {
41 | return FTry::create($this->callFunction($function, $this->value, $args));
42 | } catch (\Exception $e) {
43 | return new Failure($e);
44 | }
45 | }
46 |
47 | /**
48 | * Is this option a Success?
49 | *
50 | * @return bool
51 | */
52 | public function isSuccess(): bool
53 | {
54 | return true;
55 | }
56 |
57 | /**
58 | * Do nothing
59 | *
60 | * @return $this
61 | */
62 | public function pass()
63 | {
64 | return $this;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/FlattenAble.php:
--------------------------------------------------------------------------------
1 | value();
28 | if ($val instanceof \Closure) {
29 | return $val();
30 | }
31 | if ($val instanceof Monadic) {
32 | return $val->flatten();
33 | }
34 |
35 | return $val;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/Identity.php:
--------------------------------------------------------------------------------
1 | value = $value;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/Map.php:
--------------------------------------------------------------------------------
1 | checkHash($value)) {
39 | throw new \InvalidArgumentException('value is not a hashed array');
40 | }
41 | }
42 | parent::__construct($value, $type);
43 | }
44 |
45 | /**
46 | * Append value and return a new collection
47 | *
48 | * The appending is based on the hashed keys
49 | *
50 | * Overrides ancestor
51 | *
52 | * @param array $value
53 | *
54 | * @return Collection
55 | * @throws \InvalidArgumentException
56 | */
57 | public function append($value): Map
58 | {
59 | if (!is_array($value)) {
60 | throw new \InvalidArgumentException('Appended value must be array');
61 | }
62 | return $this->kUnion(new static($value));
63 | }
64 |
65 | /**
66 | * Value intersection is meaningless for a Map
67 | *
68 | * @inheritdoc
69 | * @throws \BadMethodCallException
70 | */
71 | final public function vIntersect(Collection $other, \Closure $function = null)
72 | {
73 | throw new \BadMethodCallException(sprintf(self::ERR_TPL_BADM, __METHOD__));
74 | }
75 |
76 | /**
77 | * Value union is meaningless for a Map
78 | *
79 | * @inheritdoc
80 | * @throws \BadMethodCallException
81 | */
82 | final public function vUnion(Collection $other, $sortOrder = SORT_REGULAR)
83 | {
84 | throw new \BadMethodCallException(sprintf(self::ERR_TPL_BADM, __METHOD__));
85 | }
86 |
87 | /**
88 | * Value difference is meaningless for a Map
89 | *
90 | * @inheritdoc
91 | * @throws \BadMethodCallException
92 | */
93 | final public function vDiff(Collection $other, \Closure $function = null)
94 | {
95 | throw new \BadMethodCallException(sprintf(self::ERR_TPL_BADM, __METHOD__));
96 | }
97 |
98 | /**
99 | * @param array $value
100 | *
101 | * @return bool
102 | */
103 | protected function checkHash(array $value): bool
104 | {
105 | return array_reduce(
106 | array_keys($value),
107 | function ($carry, $val) {
108 | if (!is_string($val)) {
109 | return false;
110 | }
111 | return $carry;
112 | },
113 | true
114 | );
115 | }
116 | }
--------------------------------------------------------------------------------
/src/chippyash/Monad/Monad.php:
--------------------------------------------------------------------------------
1 | callFunction($function, $this->value, $args));
37 | }
38 |
39 | /**
40 | * Static factory creator for the Monad
41 | *
42 | * @param $value
43 | *
44 | * @return Monadic
45 | */
46 | public static function create($value)
47 | {
48 | if ($value instanceof Monadic) {
49 | return $value;
50 | }
51 |
52 | return new static($value);
53 | }
54 |
55 | /**
56 | * Some syntactic sugar
57 | *
58 | * Proxy to bind() e.g. $ret = $foo(function($val){return $val * 2;});
59 | * Proxy to value() e.g. $val = $foo();
60 | *
61 | * @see bind()
62 | * @see value()
63 | *
64 | * @return mixed|Monadic
65 | * @throw BadMethodCallException
66 | */
67 | public function __invoke()
68 | {
69 | if (func_num_args() == 0) {
70 | return $this->value();
71 | }
72 | if (func_get_arg(0) instanceof \Closure) {
73 | return call_user_func_array(array($this, 'bind'), func_get_args());
74 | }
75 |
76 | throw new \BadMethodCallException('Invoke could not match value() or bind()');
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/Monadic.php:
--------------------------------------------------------------------------------
1 | value();
49 | }
50 |
51 | return $elseValue;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/Option/None.php:
--------------------------------------------------------------------------------
1 | value = $value;
27 | }
28 |
29 | /**
30 | * Return Some or None as a result of bind
31 | *
32 | * @param \Closure $function
33 | * @param array $args
34 | * @param mixed $noneValue Optional value to test for None
35 | *
36 | * @return Some|None
37 | */
38 | public function bind(\Closure $function, array $args = [], $noneValue = null): Option
39 | {
40 | return Option::create($this->callFunction($function, $this->value, $args), $noneValue);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/ReturnValueAble.php:
--------------------------------------------------------------------------------
1 | value;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/chippyash/Monad/Set.php:
--------------------------------------------------------------------------------
1 | exchangeArray($this->checkUniqueness($value));
39 | }
40 | }
41 |
42 | /**
43 | * Returns a Set containing all the values of this Set that are present
44 | * in the other Set.
45 | *
46 | * If the optional comparison function is supplied it must have signature
47 | * function(mixed $a, mixed $b){}. The comparison function must return an integer
48 | * less than, equal to, or greater than zero if the first argument is considered
49 | * to be respectively less than, equal to, or greater than the second.
50 | *
51 | * If the comparison function is not supplied, a built in one will be used
52 | *
53 | * @param Set $other
54 | * @param callable|\Closure $function Optional function to compare values
55 | *
56 | * @return Set
57 | */
58 | public function vIntersect(Collection $other, \Closure $function = null): Set
59 | {
60 | $function = (is_null($function) ? $this->equalityFunction() : $function);
61 |
62 | return parent::vIntersect($other, $function);
63 | }
64 |
65 | /**
66 | * Compares this collection against another collection using its values for
67 | * comparison and returns a new Collection with the values in this collection
68 | * that are not present in the other collection.
69 | *
70 | * Note that keys are preserved
71 | *
72 | * If the optional comparison function is supplied it must have signature
73 | * function(mixed $a, mixed $b){}. The comparison function must return an integer
74 | * less than, equal to, or greater than zero if the first argument is considered
75 | * to be respectively less than, equal to, or greater than the second.
76 | *
77 | * If the comparison function is not supplied, a built in one will be used
78 | *
79 | * @param Set $other
80 | * @param \Closure $function optional function to compare values
81 | *
82 | * @return Set
83 | */
84 | public function vDiff(Collection $other, \Closure $function = null): Set
85 | {
86 | $function = (is_null($function) ? $this->equalityFunction() : $function);
87 |
88 | return parent::vDiff(
89 | $other, $function
90 | );
91 | }
92 |
93 | /**
94 | * Bind monad with function. Function is in form f($value){}
95 | * You can pass additional parameters in the $args array in which case your
96 | * function should be in the form f($value, $arg1, ..., $argN)
97 | *
98 | * @param \Closure $function
99 | * @param array $args additional arguments to pass to function
100 | *
101 | * @return Set
102 | */
103 | public function bind(\Closure $function, array $args = []): Set
104 | {
105 | $res = $this->callFunction($function, $this, $args);
106 |
107 | return ($res instanceof Set ? $res : new static(is_array($res)? $res :[$res]));
108 | }
109 |
110 | /**
111 | * Key intersection is meaningless for a set
112 | *
113 | * @inheritdoc
114 | * @throws \BadMethodCallException
115 | */
116 | final public function kIntersect(Collection $other, \Closure $function = null)
117 | {
118 | throw new \BadMethodCallException(sprintf(self::ERR_TPL_BADM, __METHOD__));
119 | }
120 |
121 | /**
122 | * Key union is meaningless for a set
123 | *
124 | * @inheritdoc
125 | * @throws \BadMethodCallException
126 | */
127 | final public function kUnion(Collection $other)
128 | {
129 | throw new \BadMethodCallException(sprintf(self::ERR_TPL_BADM, __METHOD__));
130 | }
131 |
132 | /**
133 | * Key difference is meaningless for a set
134 | *
135 | * @inheritdoc
136 | * @throws \BadMethodCallException
137 | */
138 | final public function kDiff(Collection $other, \Closure $function = null)
139 | {
140 | throw new \BadMethodCallException(sprintf(self::ERR_TPL_BADM, __METHOD__));
141 | }
142 |
143 |
144 | /**
145 | * Make sure that values are unique
146 | *
147 | * @param array $values Values to check
148 | *
149 | * @return array
150 | */
151 | protected function checkUniqueness(array $values): array
152 | {
153 | try {
154 | //see if we can turn a value into a string
155 | $toTest = end($values);
156 | reset($values);
157 | (string) $toTest; //this will throw an exception if it fails
158 |
159 | //do the simple
160 | return array_values(array_unique($values));
161 | } catch (\Throwable $e) {
162 | //slower but effective
163 | return array_values(
164 | array_map(
165 | function ($key) use ($values) {
166 | return $values[$key];
167 | },
168 | array_keys(
169 | array_unique(
170 | array_map(
171 | function ($item) {
172 | return serialize($item);
173 | },
174 | $values
175 | )
176 | )
177 | )
178 | )
179 | );
180 | }
181 | }
182 |
183 | /**
184 | * Provide equality check function
185 | *
186 | * @return \Closure
187 | */
188 | private function equalityFunction(): \Closure
189 | {
190 | return function ($a, $b) {
191 | if (is_object($a)) {
192 | $a = \serialize($a);
193 | $b = \serialize($b);
194 | }
195 |
196 | return ($a === $b ? 0 : ($a < $b ? -1 : 1));
197 | };
198 | }
199 | }
--------------------------------------------------------------------------------
/test/.gitignore:
--------------------------------------------------------------------------------
1 | .phpunit.result.cache
--------------------------------------------------------------------------------
/test/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/test/src/chippyash/FTry/FailureTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Failure::class, new Failure(new \Exception()));
21 | }
22 |
23 | public function testCreateCreatesAFailureIfValueIsException()
24 | {
25 | $this->assertInstanceOf(Failure::class, Failure::create(new \Exception()));
26 | }
27 |
28 | public function testCreateCreatesAFailureIfValueIsNotAnException()
29 | {
30 | $this->assertInstanceOf(Failure::class, Failure::create('foo'));
31 | }
32 |
33 | public function testBindReturnsFailureWithSameValue()
34 | {
35 | $exc = new \Exception();
36 | $fail = Failure::create($exc);
37 | $this->assertInstanceOf(Failure::class, $fail);
38 | $this->assertInstanceOf(Failure::class, $fail->bind(function(){}));
39 | $this->assertEquals($exc, $fail->bind(function(){})->value());
40 | }
41 |
42 | public function testCallingPassWillThrowAnException()
43 | {
44 | $this->expectException(\RuntimeException::class);
45 | Failure::create('foo')->pass();
46 | }
47 |
48 | public function testCallingIsSuccessWillReturnFalse()
49 | {
50 | $this->assertFalse(Failure::create(new \Exception())->isSuccess());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/test/src/chippyash/FTry/SuccessTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Success::class, new Success('foo'));
22 | }
23 |
24 | public function testYouCannotConstructASuccessWithAnException()
25 | {
26 | $this->expectException(\RuntimeException::class);
27 | new Success(new \Exception());
28 | }
29 |
30 | public function testBindingASuccessWithSomethingThatDoesNotThrowAnExceptionWillReturnSuccess()
31 | {
32 | $sut = new Success('foo');
33 | $this->assertInstanceOf(Success::class, $sut->bind(function(){return true;}));
34 | }
35 |
36 | public function testBindingASuccessWithSomethingThatReturnsASuccessWillFlattenTheValue()
37 | {
38 | $sut = new Success('foo');
39 | $binded = $sut->bind(function () {
40 | return new Success('bar');
41 | });
42 | $this->assertInstanceOf(Success::class, $binded);
43 | $this->assertEquals('bar', $binded->value());
44 | }
45 |
46 | public function testBindingASuccessWithSomethingThatThrowsAnExceptionWillReturnAFailure()
47 | {
48 | $sut = new Success('foo');
49 | $this->assertInstanceOf(Failure::class, $sut->bind(function(){throw new \Exception();}));
50 | }
51 |
52 | public function testYouCanGetAValueFromASuccess()
53 | {
54 | $sut = new Success('foo');
55 | $this->assertEquals('foo', $sut->value());
56 | }
57 |
58 | public function testCallingIsSuccessWillReturnTrue()
59 | {
60 | $this->assertTrue(Success::create('foo')->isSuccess());
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/CollectionTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Collection::class, new Collection(['foo']));
21 | }
22 |
23 | public function testYouCannotConstructACollectionWithNullValues()
24 | {
25 | $this->expectException(\RuntimeException::class);
26 | $this->assertInstanceOf(Collection::class, new Collection([null]));
27 | }
28 |
29 | public function testYouCannotConstructACollectionWithAnEmptyArrayAndNoTypeSpecified()
30 | {
31 | $this->expectException(\RuntimeException::class);
32 | new Collection([]);
33 | }
34 |
35 | public function testYouCanConstructAnEmptyCollectionIfYouPassAType()
36 | {
37 | $this->assertInstanceOf(Collection::class, new Collection([], 'string'));
38 | $this->assertInstanceOf(Collection::class, new Collection([], Monad::class));
39 | }
40 |
41 | public function testWhenConstructingACollectionYouMustHaveSameTypeValues()
42 | {
43 | $this->assertInstanceOf(Collection::class, new Collection(['foo','bar','baz'], 'string'));
44 | }
45 |
46 | public function testConstructingACollectionWithDissimilarTypesWillCauseAnException()
47 | {
48 | $this->expectException(\RuntimeException::class);
49 | new Collection(['foo',new \StdClass(),'baz'], 'string');
50 | }
51 |
52 | public function testYouCanCreateACollection()
53 | {
54 | $this->assertInstanceOf(Collection::class, Collection::create(['foo']));
55 | }
56 |
57 | public function testTheValueOfACollectionIsTheCollection()
58 | {
59 | $collection = Collection::create(['foo']);
60 | $this->assertEquals($collection, $collection->value());
61 | }
62 |
63 | public function testYouCanBindAFunctionToTheEntireCollectionAndReturnACollection()
64 | {
65 | $sut = Collection::create([2,3,4,5,6]);
66 | //function returns a single value - converted to a collection
67 | $f = function($c){
68 | return $c[0];
69 | };
70 | $this->assertEquals([2], $sut->bind($f)->getArrayCopy());
71 | //function returns a single value - converted to collection
72 | $f2 = function($c){
73 | return 'foo';
74 | };
75 | $this->assertEquals(['foo'], $sut->bind($f2)->getArrayCopy());
76 | //function returns a collection
77 | $f3 = function($c) {
78 | return new Collection(array_flip($c->toArray()));
79 | };
80 | $this->assertEquals([2=>0, 3=>1, 4=>2, 5=>3, 6=>4], $sut->bind($f3)->getArrayCopy());
81 | //function returns and array - converted to a collection
82 | $f4 = function($c){
83 | return $c->toArray();
84 | };
85 | $this->assertEquals([2,3,4,5,6], $sut->bind($f4)->getArrayCopy());
86 | }
87 |
88 | public function testYouCanBindAFunctionToEachMemberOfTheCollectionAndReturnACollection()
89 | {
90 | $sut = Collection::create([2,3,4,5,6]);
91 | $res = $sut->each(function($v){return $v * 2;});
92 | $this->assertInstanceOf(Collection::class, $res);
93 | $this->assertEquals([4,6,8,10,12], $res->getArrayCopy());
94 | }
95 |
96 | public function testYouCanCountTheItemsInTheCollection()
97 | {
98 | $this->assertEquals(4, Collection::create([1,2,3,4])->count());
99 | }
100 |
101 | public function testYouCanGetAnIteratorForACollection()
102 | {
103 | $this->assertInstanceOf('ArrayIterator', Collection::create([1,2,3,4])->getIterator());
104 | }
105 |
106 | public function testYouCannotUnsetACollectionMember()
107 | {
108 | $this->expectException(\BadMethodCallException::class);
109 | Collection::create([1,2,3])->offsetUnset(2);
110 | }
111 |
112 | public function testYouCannotSetACollectionMember()
113 | {
114 | $this->expectException(\BadMethodCallException::class);
115 | Collection::create([1,2,3])->offsetSet(2, 6);
116 | }
117 |
118 | public function testYouCanGetACollectionMemberAsAnArrayOffset()
119 | {
120 | $sut = Collection::create([1,2,3]);
121 | $this->assertEquals(2, $sut->offsetGet(1));
122 | $this->assertEquals(2, $sut[1]);
123 | }
124 |
125 | public function testYouCanTestIfACollectionMemberExistsAsAnArrayOffset()
126 | {
127 | $sut = Collection::create([1,2,3]);
128 | $this->assertTrue(isset($sut[1]));
129 | $this->assertFalse(isset($sut[99]));
130 | }
131 |
132 | public function testYouCanCreateACollectionOfCollections()
133 | {
134 | $s1 = Collection::create([1,2,3]);
135 | $s2 = Collection::create([5,6,7]);
136 | $s3 = Collection::create([$s1, $s2]);
137 | $this->assertInstanceOf(Collection::class, $s3);
138 | }
139 |
140 | public function testFlatteningACollectionOfCollectionsWillReturnACollection()
141 | {
142 | $s1 = Collection::create([1,2,3]);
143 | $s2 = Collection::create([5,6,7]);
144 | $flattened = Collection::create([$s1, $s2])->flatten();
145 | $this->assertInstanceOf(Collection::class, $flattened);
146 | foreach ($flattened as $value) {
147 | $this->assertInstanceOf(Collection::class, $value);
148 | }
149 | }
150 |
151 | public function testYouCanGetTheDifferenceOfValuesBetweenTwoCollections()
152 | {
153 | $s1 = Collection::create([1, 2, 3, 6, 7]);
154 | $s2 = Collection::create([6,7]);
155 | $this->assertEquals([1,2,3], $s1->vDiff($s2)->flatten()->toArray());
156 | }
157 |
158 | public function testYouCanGetTheDifferenceOfKeysBetweenTwoCollections()
159 | {
160 | $s1 = Collection::create([1 => 0, 2 => 0, 3 => 0, 6 => 0, 7 => 0]);
161 | $s2 = Collection::create([6 => 0,7 => 0]);
162 | $this->assertEquals([1 => 0,2 => 0,3 => 0], $s1->kDiff($s2)->flatten()->toArray());
163 | }
164 |
165 | public function testYouCanChainVDiffMethodsToActOnArbitraryNumbersOfCollections()
166 | {
167 | $s1 = Collection::create([1, 2, 3, 6, 7]);
168 | $s2 = Collection::create([6,7]);
169 | $s3 = Collection::create([1]);
170 | $s4 = Collection::create([9]);
171 |
172 | $this->assertEquals([2,3], array_values($s1->vDiff($s2)->vDiff($s3)->vDiff($s4)->flatten()->toArray()));
173 | }
174 |
175 | public function testYouCanChainKDiffMethodsToActOnArbitraryNumbersOfCollections()
176 | {
177 | $s1 = Collection::create([1 => 0, 2 => 0, 3 => 0, 6 => 0, 7 => 0]);
178 | $s2 = Collection::create([6 => 0, 7 => 0]);
179 | $s3 = Collection::create([1 => 0]);
180 | $s4 = Collection::create([9 => 0]);
181 |
182 | $this->assertEquals([2 => 0, 3 => 0], $s1->kDiff($s2)->kDiff($s3)->kDiff($s4)->flatten()->toArray());
183 | }
184 |
185 | public function testYouCanSupplyAnOptionalComparatorFunctionToVDiffMethod()
186 | {
187 | $s1 = Collection::create([1, 2, 3, 6, 7]);
188 | $s2 = Collection::create([6,7]);
189 | $f = function($a, $b){
190 | return ($a<$b ? -1 : ($a>$b ? 1 : 0));
191 | };
192 | $this->assertEquals([1,2,3], $s1->vDiff($s2, $f)->flatten()->toArray());
193 | }
194 |
195 | public function testYouCanSupplyAnOptionalComparatorFunctionToKDiffMethod()
196 | {
197 | $s1 = Collection::create([1 => 0, 2 => 0, 3 => 0, 6 => 0, 7 => 0]);
198 | $s2 = Collection::create([6 => 0,7 => 0]);
199 | $f = function($a, $b){
200 | return ($a<$b ? -1 : ($a>$b ? 1 : 0));
201 | };
202 | $this->assertEquals([1 => 0,2 => 0,3 => 0], $s1->kDiff($s2, $f)->flatten()->toArray());
203 | }
204 |
205 | public function testYouCanGetTheIntersectionOfTwoCollectionsByValue()
206 | {
207 | $s1 = Collection::create([1, 2, 3, 6, 7]);
208 | $s2 = Collection::create([6,7]);
209 | $this->assertEquals([6,7], array_values($s1->vIntersect($s2)->flatten()->toArray()));
210 | }
211 |
212 | public function testYouCanGetTheIntersectionOfTwoCollectionsByKey()
213 | {
214 | $s1 = Collection::create([1, 2, 3, 6, 7]);
215 | $s2 = Collection::create([6,7]);
216 | $this->assertEquals([1,2], array_values($s1->kIntersect($s2)->flatten()->toArray()));
217 | }
218 |
219 | public function testYouCanChainValueIntersectMethodsToActOnArbitraryNumbersOfCollections()
220 | {
221 | $s1 = Collection::create([1, 2, 3, 6, 7]);
222 | $s2 = Collection::create([6,7]);
223 | $s3 = Collection::create([7]);
224 |
225 | $this->assertEquals([7], array_values($s1->vIntersect($s2)->vIntersect($s3)->flatten()->toArray()));
226 | }
227 |
228 | public function testYouCanChainKeyIntersectMethodsToActOnArbitraryNumbersOfCollections()
229 | {
230 | $s1 = Collection::create([1, 2, 3, 6, 7]);
231 | $s2 = Collection::create([6,7]);
232 | $s3 = Collection::create([7]);
233 |
234 | $this->assertEquals([0=>1], array_values($s1->kIntersect($s2)->kIntersect($s3)->flatten()->toArray()));
235 | }
236 |
237 | public function testYouCanSupplyAnOptionalComparatorFunctionToTheValueIntersectMethod()
238 | {
239 | $s1 = Collection::create([1, 2, 3, 6, 7]);
240 | $s2 = Collection::create([6,7]);
241 | $f = function($a, $b){
242 | return ($a<$b ? -1 : ($a>$b ? 1 : 0));
243 | };
244 | $this->assertEquals([6, 7], array_values($s1->vIntersect($s2, $f)->flatten()->toArray()));
245 | }
246 |
247 | public function testYouCanSupplyAnOptionalComparatorFunctionToTheKeyIntersectMethod()
248 | {
249 | $s1 = Collection::create([1, 2, 3, 6, 7]);
250 | $s2 = Collection::create([6,7]);
251 | $f = function($a, $b){
252 | return ($a<$b ? -1 : ($a>$b ? 1 : 0));
253 | };
254 | $this->assertEquals([0=>1, 1=>2], array_values($s1->kIntersect($s2, $f)->flatten()->toArray()));
255 | }
256 |
257 | public function testYouCanGetTheUnionOfValuesOfTwoCollections()
258 | {
259 | $s1 = Collection::create([1, 2, 3, 6, 7]);
260 | $s2 = Collection::create([3, 6, 7, 8]);
261 | $this->assertEquals([1,2,3,6,7,8], array_values($s1->vUnion($s2)->flatten()->toArray()));
262 | }
263 |
264 | public function testYouCanChainTheUnionOfValuesOfTwoCollections()
265 | {
266 | $s1 = Collection::create([1, 2, 3, 6, 7]);
267 | $s2 = Collection::create([3, 6, 7, 8]);
268 | $s3 = Collection::create([7, 8, 9, 10]);
269 | $this->assertEquals([1,2,3,6,7,8,9,10], array_values($s1->vUnion($s2)->vUnion($s3)->flatten()->toArray()));
270 | }
271 |
272 | public function testYouCanGetTheUnionOfKeysOfTwoCollections()
273 | {
274 | $s1 = Collection::create([1, 2, 3, 6, 7]);
275 | $s2 = Collection::create([0, 0, 3, 6, 7, 8]);
276 | $this->assertEquals([0=>1,1=>2,2=>3,3=>6,4=>7, 5=>8], $s1->kUnion($s2)->flatten()->toArray());
277 | }
278 |
279 | public function testYouCanChainTheUnionOfKeysOfTwoCollections()
280 | {
281 | $s1 = Collection::create([1, 2, 3, 6, 7]);
282 | $s2 = Collection::create([0, 0, 3, 6, 7, 8]);
283 | $s3 = Collection::create([0, 0, 0, 7, 8, 9, 10]);
284 | $this->assertEquals(
285 | [0=>1, 1=>2, 2=>3, 3=>6,4=>7, 5=>8, 6=>10],
286 | $s1->kUnion($s2)->kUnion($s3)->flatten()->toArray()
287 | );
288 | }
289 |
290 | public function testPerformingAValueUnionWithDissimilarCollectionsWillThrowAnException()
291 | {
292 | $this->expectException(\RuntimeException::class);
293 | (new Collection([],'string'))->vUnion(new Collection([1]));
294 | }
295 |
296 | public function testPerformingAKeyUnionWithDissimilarCollectionsWillThrowAnException()
297 | {
298 | $this->expectException(\RuntimeException::class);
299 | (new Collection([],'string'))->kUnion(new Collection([1]));
300 | }
301 |
302 | public function testTheHeadOfACollectionIsItsFirstMember()
303 | {
304 | $s1 = Collection::create([1, 2, 3, 6, 7]);
305 | $this->assertEquals([1], $s1->head()->flatten()->toArray());
306 | }
307 |
308 | public function testTheTailOfACollectionIsAllButItsFirstMember()
309 | {
310 | $s1 = Collection::create([1, 2, 3, 6, 7]);
311 | $this->assertEquals([2, 3, 6, 7], $s1->tail()->flatten()->toArray());
312 | }
313 |
314 | public function testYouCanFilterACollectionWithAClosure()
315 | {
316 | $f = function($v){return $v>3;};
317 | $s1 = Collection::create([1, 2, 3, 6, 7]);
318 | $this->assertEquals([3=>6, 4=>7], $s1->filter($f)->toArray());
319 | }
320 |
321 | public function testYouCanReduceACollectionToASingleValueWithAClosure()
322 | {
323 | $f = function($v, $carry){return $carry + $v;};
324 | $s1 = Collection::create([1, 2, 3, 6, 7]);
325 | $this->assertEquals(29, $s1->reduce($f, 10));
326 | }
327 |
328 | public function testYouCanReferenceACollectionAsThoughItWasAnArray()
329 | {
330 | $s1 = Collection::create([1, 2, 3, 6, 7]);
331 | $this->assertEquals(2, $s1[1]);
332 | }
333 |
334 | public function testValueMethodProxiesToCollectionGetArrayCopyMethod()
335 | {
336 | $s1 = Collection::create([1, 2, 3, 6, 7]);
337 | $this->assertEquals($s1->toArray(), $s1->getArrayCopy());
338 | }
339 |
340 | public function testYouCanFlipACollection()
341 | {
342 | $s1 = Collection::create([1, 2, 3, 6, 7])->flip()->toArray();
343 | $this->assertEquals([1=>0, 2=>1, 3=>2, 6=>3, 7=>4], $s1);
344 | }
345 |
346 | public function testAppendingToACollectionReturnsANewCollection()
347 | {
348 | $s1 = Collection::create([1, 2, 3, 6, 7]);
349 | $s2 = $s1->append(8);
350 | $this->assertEquals([1,2,3,6,7,8], $s2->toArray());
351 | }
352 | }
353 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/FMatchTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(FMatch::class, new FMatch('foo'));
26 | }
27 |
28 | public function testYouCanConstructViaStaticOnFactoryMethod()
29 | {
30 | $this->assertInstanceOf(FMatch::class, FMatch::on('foo'));
31 | }
32 |
33 | public function testYouCanFMatchOnNativePhpTypes()
34 | {
35 | $this->assertEquals('foo', FMatch::on('foo')->string()->value());
36 | $this->assertEquals(1, FMatch::on(1)->int()->value());
37 | $this->assertEquals(1, FMatch::on(1)->integer()->value());
38 | $this->assertEquals(1.0, FMatch::on(1.0)->float()->value());
39 | $this->assertEquals(1.0, FMatch::on(1.0)->double()->value());
40 | $this->assertNull(FMatch::on(null)->null()->value());
41 | $this->assertEquals([], FMatch::on([])->array()->value());
42 | $this->assertTrue(FMatch::on(true)->bool()->value());
43 | $this->assertTrue(FMatch::on(true)->boolean()->value());
44 | $this->assertInstanceOf(\Closure::class, FMatch::on(function(){})->callable()->value());
45 | $this->assertInstanceOf(\Closure::class, FMatch::on(function(){})->function()->value());
46 | $this->assertInstanceOf(\Closure::class, FMatch::on(function(){})->closure()->value());
47 | $this->assertTrue(is_object(FMatch::on(new \stdClass())->object()->value()));
48 | $this->assertEquals(123, FMatch::on(123)->scalar()->value());
49 | $this->assertEquals(123, FMatch::on('123')->numeric()->value());
50 | $this->assertEquals(123, FMatch::on(123)->numeric()->value());
51 |
52 | $fileRoot = vfsStream::setup();
53 | $this->assertEquals('vfs://root', FMatch::on($fileRoot->url())->dir()->value());
54 | $this->assertEquals('vfs://root', FMatch::on($fileRoot->url())->directory()->value());
55 |
56 | $fileRoot->addChild(new vfsStreamFile('foo'));
57 | $this->assertEquals('vfs://root/foo', FMatch::on($fileRoot->url() . '/foo')->file()->value());
58 |
59 | $fh = fopen($fileRoot->url() . '/foo', 'r');
60 | $this->assertTrue(is_resource(FMatch::on($fh)->resource()->value()));
61 | fclose($fh);
62 | }
63 |
64 | public function testFMatchingWillReturnSetByCallableParameterIfFMatched()
65 | {
66 | $this->assertEquals('foobarfoo', FMatch::on('foobar')->string(function($val){return $val . 'foo';})->value());
67 | }
68 |
69 | public function testFMatchingWillReturnSetByNonCallableParameterIfFMatched()
70 | {
71 | $this->assertEquals('foobarfoo', FMatch::on('foobar')->string('foobarfoo')->value());
72 | }
73 |
74 | public function testFailingToFMatchWillReturnNewFMatchObjectWithSameValueAsOriginal()
75 | {
76 | $this->assertEquals(true, FMatch::on(true)->string()->value());
77 | }
78 |
79 | public function testYouCanFMatchOnAClassName()
80 | {
81 | $this->assertInstanceOf('StdClass', FMatch::on(new \StdClass)->StdClass()->value());
82 | $this->assertInstanceOf('Monad\FMatch', FMatch::on(new FMatch('foo'))->Monad_FMatch()->value());
83 | }
84 |
85 | public function testFailingToFMatchOnClassNameWillReturnNewFMatchObjectWithSameValueAsOriginal()
86 | {
87 | $val = new Identity('foo');
88 | $test = FMatch::on($val)->StdClass();
89 | $this->assertInstanceOf(FMatch::class, $test);
90 | $this->assertInstanceOf(Identity::class, $test->value());
91 | $this->assertEquals('foo', $test->flatten());
92 | }
93 |
94 | public function testYouCanChainFMatchTests()
95 | {
96 | $test = FMatch::on(true)
97 | ->string()
98 | ->int()
99 | ->bool()
100 | ->value();
101 |
102 | $this->assertTrue($test);
103 | }
104 |
105 | public function testYouCanChainFMatchTestsAndBindAFunctionOnSuccessfulFMatch()
106 | {
107 | $test = FMatch::on(true)
108 | ->string()
109 | ->int()
110 | ->bool(function(){return 'foo';})
111 | ->value();
112 |
113 | $this->assertEquals('foo', $test);
114 | }
115 |
116 | public function testBindingAFMatchWillReturnAFMatch()
117 | {
118 | $test = FMatch::on('foo')->bind(function($v){return $v . 'bar';});
119 | $this->assertInstanceOf(FMatch::class, $test);
120 | $this->assertEquals('foobar', $test->value());
121 | }
122 |
123 | /**
124 | * @dataProvider anyFMatchData
125 | * @param $value
126 | */
127 | public function testFMatchOnAnyMethodWillFMatchAnything($value)
128 | {
129 | $this->assertEquals(
130 | $value,
131 | FMatch::on($value)
132 | ->any()
133 | ->value()
134 | );
135 | }
136 |
137 | /**
138 | * @dataProvider anyFMatchData
139 | * @param $value
140 | */
141 | public function testFMatchOnAnyMethodCanAcceptOptionalFunctionAndArguments($value)
142 | {
143 | $this->assertEquals(
144 | 'bar',
145 | FMatch::on($value)
146 | ->any(
147 | function($v, $z){return $z;},
148 | ['bar']
149 | )
150 | ->value()
151 | );
152 | }
153 |
154 | public function anyFMatchData()
155 | {
156 | date_default_timezone_set('UTC');
157 | return [
158 | [2],
159 | ['foo'],
160 | [1.13],
161 | [new \DateTime()],
162 | [new \StdClass()],
163 | [true],
164 | [false]
165 | ];
166 | }
167 |
168 | public function testYouCanNestFMatches()
169 | {
170 | $this->assertEquals('foo', $this->nestedFMatcher('foo')->value());
171 | $this->assertEquals('bar', $this->nestedFMatcher(Option::create('bar'))->value());
172 | try {
173 | $this->nestedFMatcher(Option::create());
174 | $this->fail('Expected an Exception but got none');
175 | } catch (\Exception $e) {
176 | $this->assertTrue(true);
177 | }
178 | $this->assertEquals('foobar', $this->nestedFMatcher(Identity::create('foo'))->value());
179 | //expecting match on any() as integer won't be matched
180 | $this->assertEquals('any', $this->nestedFMatcher(2)->value());
181 | }
182 |
183 | protected function nestedFMatcher($initialValue)
184 | {
185 | return FMatch::on($initialValue)
186 | ->string('foo')
187 | ->Monad_Option(
188 | function ($v) {
189 | return FMatch::on($v)
190 | ->Monad_Option_Some(function ($v) {
191 | return $v->value();
192 | })
193 | ->Monad_Option_None(function () {
194 | throw new \Exception();
195 | })
196 | ->value();
197 | }
198 | )
199 | ->Monad_Identity(
200 | function ($v) {
201 | return $v->value() . 'bar';
202 | }
203 | )
204 | ->any(
205 | function() {
206 | return 'any';
207 | }
208 | );
209 | }
210 |
211 | public function testYouCanTestForEquality()
212 | {
213 | $test = FMatch::on('foo')
214 | ->test('foo')
215 | ->value();
216 | $this->assertEquals('foo', $test);
217 |
218 | $test = FMatch::on('foo')
219 | ->test('bar')
220 | ->value();
221 | $this->assertEquals('foo', $test);
222 |
223 | $test = FMatch::on('foo')
224 | ->test('foo', function($v){return new Some($v);})
225 | ->flatten();
226 | $this->assertEquals('foo', $test);
227 |
228 | $test = FMatch::on('bar')
229 | ->test('foo', function($v){return new Some($v);})
230 | ->any(function(){return new None();})
231 | ->value();
232 | $this->assertInstanceOf('Monad\Option\None', $test);
233 |
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/FTryTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Success::class, FTry::create('foo'));
24 | $this->assertEquals('foo', FTry::create('foo')->value());
25 |
26 | $this->assertInstanceOf(Success::class, FTry::create(function(){return true;}));
27 | $this->assertInstanceOf(\Closure::class, FTry::create(function(){return true;})->value());
28 |
29 | $this->assertTrue(FTry::create(function(){return true;})->flatten());
30 | $this->assertInstanceOf(Success::class, FTry::create(new Identity('foo')));
31 |
32 | $this->assertEquals('foo', FTry::create(new Identity('foo'))->flatten());
33 | }
34 |
35 | public function testCreatingAnFTryWithAnExceptionWillReturnAFailure()
36 | {
37 | $this->assertInstanceOf(Failure::class, FTry::create(new \Exception()));
38 | $this->assertInstanceOf(\Exception::class, FTry::create(new \Exception())->value());
39 |
40 | $this->assertInstanceOf(Failure::class, FTry::create(function(){throw new \Exception();}));
41 | $this->assertInstanceOf(\Exception::class, FTry::create(function(){throw new \Exception();})->value());
42 |
43 | $this->assertInstanceOf(Failure::class, FTry::create(new Identity(function(){throw new \Exception();})));
44 | $this->assertInstanceOf(\Exception::class, FTry::create(new Identity(function(){throw new \Exception();}))->value());
45 | }
46 |
47 | public function testTheWithMethodProxiesToCreate()
48 | {
49 | $this->assertInstanceOf(Success::class, FTry::with('foo'));
50 | $this->assertInstanceOf(Success::class, FTry::with(function(){return true;}));
51 | $this->assertInstanceOf(Success::class, FTry::with(new Identity('foo')));
52 | $this->assertInstanceOf(Failure::class, FTry::with(new \Exception()));
53 | $this->assertInstanceOf(Failure::class, FTry::with(function(){throw new \Exception();}));
54 | $this->assertInstanceOf(Failure::class, FTry::with(new Identity(function(){throw new \Exception();})));
55 | }
56 |
57 | public function testGetOrElseWillReturnFTryValueIfOptionIsASuccess()
58 | {
59 | $sut = FTry::create(true);
60 | $this->assertTrue($sut->getOrElse(false));
61 | }
62 |
63 | public function testGetOrElseWillReturnElseValueIfFTryIsAFailure()
64 | {
65 | $sut = FTry::create(new \Exception());
66 | $this->assertFalse($sut->getOrElse(false));
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/IdentityTest.php:
--------------------------------------------------------------------------------
1 | sut = new Identity('foo');
26 | }
27 |
28 | public function testYouCanCreateAnIdentityStatically()
29 | {
30 | $this->assertInstanceOf(Identity::class, Identity::create('bar'));
31 | }
32 |
33 | public function testCreatingAnIdentityWithAnIdentityParameterWillReturnTheParameter()
34 | {
35 | $this->assertEquals($this->sut, Identity::create($this->sut));
36 | }
37 |
38 | public function testCreatingAnIdentityWithANonIdentityParameterWillReturnAnIdentityContainingTheParameterAsValue()
39 | {
40 | $sut = Identity::create('foo');
41 | $this->assertInstanceOf(Identity::class, $sut);
42 | $this->assertEquals('foo', $sut->value());
43 |
44 | $sut1 = Identity::create(function($a){return $a;});
45 | $this->assertInstanceOf(Identity::class, $sut1);
46 | $this->assertInstanceOf(\Closure::class, $sut1->value());
47 | }
48 |
49 | public function testYouCanBindAFunctionOnAnIdentity()
50 | {
51 | $func = function ($value) {
52 | return $value . 'bar';
53 | };
54 | $this->assertEquals('foobar', $this->sut->bind($func)->value());
55 |
56 | $identity = new Identity($this->sut);
57 | $this->assertEquals('foobar', $identity->bind($func)->value());
58 | }
59 |
60 | public function testBindCanTakeOptionalAdditionalParameters()
61 | {
62 | $func = function ($value, $fudge) {
63 | return $value . $fudge;
64 | };
65 | $this->assertEquals('foobar', $this->sut->bind($func, ['bar'])->value());
66 |
67 | $identity = new Identity($this->sut);
68 | $this->assertEquals('foobar', $identity->bind($func, ['bar'])->value());
69 | }
70 |
71 | public function testYouCanChainBindMethodsTogether()
72 | {
73 | $sut = new Identity(10);
74 | $val = $sut
75 | ->bind(function($v){return $v * 10;})
76 | ->bind(function($v, $n){return $v - $n;}, [2])
77 | ->value();
78 | $this->assertEquals(98, $val);
79 | }
80 |
81 |
82 | public function testBindingOnAnIdentityWithAClosureValueWillEvaluateTheValue()
83 | {
84 | $sut = new Identity(function(){return 'foo';});
85 | $func = function ($value) {
86 | return $value . 'bar';
87 | };
88 | $this->assertEquals('foobar', $sut->bind($func)->value());
89 | }
90 |
91 | public function testYouCanFlattenAnIdentityValueToItsBaseType()
92 | {
93 | $sut = new Identity(function(){return 'foo';});
94 | $func = function ($value) {
95 | return $value . 'bar';
96 | };
97 | $this->assertEquals('foobar', $sut->bind($func)->flatten());
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/MapTest.php:
--------------------------------------------------------------------------------
1 | expectException(\RuntimeException::class);
19 | new Map();
20 | }
21 |
22 | public function testMapsRequireStringKeys()
23 | {
24 | $this->expectException(\InvalidArgumentException::class);
25 | $this->expectExceptionMessage('value is not a hashed array');
26 | new Map(['a','b']);
27 | }
28 |
29 | public function testYouCanConstructAnEmptyMapIfYouPassAType()
30 | {
31 | $this->assertInstanceOf(Map::class, new Map([], 'string'));
32 | $this->assertInstanceOf(Map::class, new Map([], 'Monad\Identity'));
33 | }
34 |
35 | public function testAppendingToAMapReturnsANewMap()
36 | {
37 | $orig = new Map([], 'string');
38 | $new = $orig->append(['foo' => 'bar']);
39 | $this->assertEquals(['foo' => 'bar'], $new->toArray());
40 | }
41 |
42 | public function testAppendingToAMapWithUnhashedValuesThrowsAnException()
43 | {
44 | $this->expectException(\InvalidArgumentException::class);
45 | $this->expectExceptionMessage('value is not a hashed array');
46 | $orig = new Map([], 'string');
47 | $orig->append(['bar']);
48 | }
49 |
50 | public function testVunionMethodIsNotSupportedForMaps()
51 | {
52 | $this->expectException(\BadMethodCallException::class);
53 | $this->expectExceptionMessage('vUnion is not a supported method for Maps');
54 | $setA = Map::create(['a' =>0, 'b' => 0]);
55 | $setB = Map::create(['c' =>0, 'd' => 0]);
56 | $setA->vUnion($setB);
57 | }
58 |
59 | public function testVintersectMethodIsNotSupportedForMaps()
60 | {
61 | $this->expectException(\BadMethodCallException::class);
62 | $this->expectExceptionMessage('vIntersect is not a supported method for Maps');
63 | $setA = Map::create(['a' =>0, 'b' => 0]);
64 | $setB = Map::create(['c' =>0, 'd' => 0]);
65 | $setA->vIntersect($setB);
66 | }
67 |
68 | public function testVdiffMethodIsNotSupportedForMaps()
69 | {
70 | $this->expectException(\BadMethodCallException::class);
71 | $this->expectExceptionMessage('vDiff is not a supported method for Maps');
72 | $setA = Map::create(['a' =>0, 'b' => 0]);
73 | $setB = Map::create(['c' =>0, 'd' => 0]);
74 | $setA->vDiff($setB);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/MonadTest.php:
--------------------------------------------------------------------------------
1 | sut = $this->getMockForAbstractClass(Monad::class);
27 | }
28 |
29 | public function testYouCanReturnAValueWhenMonadCreatedWithSimpleValue()
30 | {
31 | $this->setSutValue('foo');
32 | $this->assertEquals('foo', $this->sut->value());
33 | }
34 |
35 | public function testYouCanReturnAValueWhenMonadCreatedWithMonadicValue()
36 | {
37 | $bound = $this->createMonadWithValue('foo');
38 | $this->setSutValue($bound);
39 | $this->assertInstanceOf(Monadic::class, $this->sut->value());
40 | }
41 |
42 | public function testYouCanUseAClosureForValue()
43 | {
44 | $this->setSutValue(function(){return 'foo';});
45 | $this->assertInstanceOf(\Closure::class, $this->sut->value());
46 | }
47 |
48 | public function testFlattenWillReturnBaseType()
49 | {
50 | $this->setSutValue('foo');
51 | $this->assertEquals('foo', $this->sut->flatten());
52 | $this->setSutValue(function(){return 'foo';});
53 | $this->assertEquals('foo', $this->sut->flatten());
54 | $this->setSutValue($this->createMonadWithValue('foo'));
55 | $this->assertEquals('foo', $this->sut->flatten());
56 | }
57 |
58 | public function testYouCanBindAClosureOnAMonadToCreateANewMonadOfTheSameType()
59 | {
60 | $this->setSutValue('foo');
61 | $fn = function($value){return $value;};
62 | $this->assertEquals(get_class($this->sut), get_class($this->sut->bind($fn)));
63 |
64 | $this->setSutValue($this->createMonadWithValue('foo'));
65 | $this->assertEquals(get_class($this->sut), get_class($this->sut->bind($fn)));
66 |
67 | $this->setSutValue(function(){return 'foo';});
68 | $this->assertEquals(get_class($this->sut), get_class($this->sut->bind($fn)));
69 | }
70 |
71 | public function testBindCanTakeOptionalAdditionalParameters()
72 | {
73 | $fn = function ($value, $fudge) {
74 | return $value . $fudge;
75 | };
76 | $this->assertEquals(get_class($this->sut), get_class($this->sut->bind($fn, ['bar'])));
77 | }
78 |
79 | public function testMagicInvokeProxiesToBindMethodIfPassedAClosure()
80 | {
81 | $this->setSutValue('foo');
82 | $fn = function ($value) {
83 | return $value . 'bar';
84 | };
85 | $sut = $this->sut;
86 | $this->assertEquals(get_class($sut), get_class($sut($fn)));
87 | }
88 |
89 | public function testMagicInvokeProxiesToValueMethodIfPassedNoParameters()
90 | {
91 | $this->setSutValue('foo');
92 | $sut = $this->sut;
93 | $this->assertEquals('foo', $sut());
94 | }
95 |
96 | public function testCallingMagicInvokeWillThrowExceptionIfNoMethodIsExecutable()
97 | {
98 | $this->expectException(\BadMethodCallException::class);
99 | $sut = $this->sut;
100 | $sut('foo');
101 | }
102 |
103 | public function testYouCannotCreateAnAbstractMonadStatically()
104 | {
105 | $refl = new \ReflectionClass(Monad::class);
106 | $this->assertNull($refl->getConstructor());
107 | }
108 |
109 | /**
110 | * Set value on the Monad SUT - Abstract Monad does not have a constructor
111 | * @param $value
112 | */
113 | private function setSutValue($value)
114 | {
115 | $refl = new \ReflectionProperty($this->sut, 'value');
116 | $refl->setAccessible(true);
117 | $refl->setValue($this->sut, $value);
118 | }
119 |
120 | /**
121 | * Create a Mock Monad with a value
122 | *
123 | * @param mixed $value
124 | * @return Monad Mock Monad
125 | */
126 | private function createMonadWithValue($value)
127 | {
128 | $monad = $this->getMockForAbstractClass(Monad::class);
129 | $refl = new \ReflectionProperty($monad, 'value');
130 | $refl->setAccessible(true);
131 | $refl->setValue($monad, $value);
132 |
133 | return $monad;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/MutableCollectionTest.php:
--------------------------------------------------------------------------------
1 | sut = new MutableCollection(['foo', 'bar', 'baz']);
26 | }
27 |
28 | public function testYouCanUnsetAMutableCollectionMember()
29 | {
30 | unset($this->sut[2]);
31 | $this->assertEquals(['foo', 'bar'], $this->sut->toArray());
32 | }
33 |
34 | public function testYouCanSetAMutableCollectionMember()
35 | {
36 | $this->sut[2] = 'bop';
37 | $this->assertEquals(['foo', 'bar', 'bop'], $this->sut->toArray());
38 | }
39 |
40 | /**
41 | * Simple test to ensure that we get back a MutableCollection as a result
42 | * of calling an underlaying Collection method as they all use `new static(...)`
43 | */
44 | public function testCallingOneOfTheUnderlayingCollectionMethodsReturnsAMutableCollection()
45 | {
46 | $this->assertInstanceOf(MutableCollection::class, $this->sut->flip());
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/Option/NoneTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(None::class, new None());
20 | }
21 |
22 | public function testYouCanConstructANoneWithAParameterAndItWillStillBeNone()
23 | {
24 | $this->assertInstanceOf(None::class, new None('foo'));
25 | }
26 |
27 | public function testCreateWillReturnANone()
28 | {
29 | $this->assertInstanceOf(None::class, None::create());
30 | }
31 |
32 | public function testBindingANoneReturnsANone()
33 | {
34 | $none = new None();
35 | $this->assertInstanceOf(None::class, $none->bind(function(){}));
36 | }
37 |
38 | public function testCallingGetOnANoneThrowsARuntimeException()
39 | {
40 | $this->expectException(\RuntimeException::class);
41 | None::create()->value();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/Option/SomeTest.php:
--------------------------------------------------------------------------------
1 | assertInstanceOf(Some::class, new Some('foo'));
21 | }
22 |
23 | public function testYouCannotConstructASomeWithNoValue()
24 | {
25 | try {
26 | new Some();
27 | } catch (\Exception $e) {
28 | //php < 7.1
29 | $this->assertInstanceOf("PHPUnit_Framework_Error_Warning", $e);
30 | } catch (\ArgumentCountError $e) {
31 | //php >= 7.1
32 | $this->assertInstanceOf("ArgumentCountError", $e);
33 | }
34 | }
35 |
36 | public function testYouCanGetAValueFromASome()
37 | {
38 | $this->assertEquals('foo', (new Some('foo'))->value());
39 | }
40 |
41 | public function testBindingOnASomeMayReturnASomeOrANone()
42 | {
43 | $this->assertInstanceOf(Some::class, (new Some('foo'))->bind(function($value){return $value;}));
44 | $this->assertInstanceOf(None::class, (new Some('foo'))->bind(function($value){return null;}));
45 | }
46 |
47 | public function testBindingOnASomeTakesAThirdNonetestValue()
48 | {
49 | $sut = new Some('foo');
50 | $this->assertInstanceOf(Some::class, $sut->bind(function($value){return true;}, [], false));
51 | $this->assertInstanceOf(None::class, $sut->bind(function($value){return false;}, [], false));
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/OptionTest.php:
--------------------------------------------------------------------------------
1 | assertNull($refl->getConstructor());
23 | $this->assertTrue($refl->isAbstract());
24 | }
25 |
26 | public function testCreatingWithAValueReturnsASome()
27 | {
28 | $sut = Option::create('foo');
29 | $this->assertInstanceOf(Some::class, $sut);
30 | }
31 |
32 | public function testCreatingWithNoValueOrNullReturnsANone()
33 | {
34 | $sut = Option::create();
35 | $this->assertInstanceOf(None::class, $sut);
36 | $sut = Option::create(null);
37 | $this->assertInstanceOf(None::class, $sut);
38 | }
39 |
40 | public function testYouCanReplaceNoneTestByCallingCreateWithAdditionalParameter()
41 | {
42 | $sut = Option::create(true, false);
43 | $this->assertInstanceOf(Some::class, $sut);
44 | $sut1 = Option::create(false, false);
45 | $this->assertInstanceOf(None::class, $sut1);
46 | }
47 |
48 | public function testGetOrElseWillReturnOptionValueIfOptionIsASome()
49 | {
50 | $sut = Option::create(true);
51 | $this->assertTrue($sut->getOrElse(false));
52 | }
53 |
54 | public function testGetOrElseWillReturnElseValueIfOptionIsANone()
55 | {
56 | $sut = Option::create(true, true);
57 | $this->assertFalse($sut->getOrElse(false));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/test/src/chippyash/Monad/SetTest.php:
--------------------------------------------------------------------------------
1 | expectException(\RuntimeException::class);
19 | new Set([]);
20 | }
21 |
22 | public function testPassingInUniqueValuesAtConstructionWillCreateASet()
23 | {
24 | $this->assertInstanceOf(Set::class, new Set(['a', 'b', 'c']));
25 | }
26 |
27 | public function testPassingInNonUniqueValuesAtConstructionWillCreateASetWithUniqueValues(
28 | )
29 | {
30 | $sut = new Set(['a', 'b', 'c', 'a', 'b', 'c']);
31 | $this->assertEquals(['a', 'b', 'c'], $sut->toArray());
32 | }
33 |
34 | public function testYouCanCreateSetsOfObjects()
35 | {
36 | $a = new \stdClass();
37 | $a->val = 'a';
38 | $b = new \stdClass();
39 | $b->val = 'b';
40 | $c = new \stdClass();
41 | $c->val = 'c';
42 |
43 | $this->assertInstanceOf(Set::class, new Set([$a, $b, $c]));
44 |
45 | $d = new \stdClass();
46 | $d->val = 'a';
47 | $e = new \stdClass();
48 | $e->val = 'b';
49 |
50 | $sut = new Set([$a, $b, $c, $d, $e]);
51 | $this->assertInstanceOf(Set::class, $sut);
52 | $test = array_map(
53 | function ($v) {
54 | return $v->val;
55 | },
56 | $sut->toArray()
57 | );
58 | $this->assertEquals(['a', 'b', 'c'], $test);
59 | }
60 |
61 | public function testYouCanCreateSetsOfResources()
62 | {
63 | $a = opendir(__DIR__);
64 | $b = opendir(__DIR__);
65 | $sut = new Set([$a, $b]);
66 | $this->assertInstanceOf(Set::class, $sut);
67 | closedir($a);
68 | closedir($b);
69 | }
70 |
71 | public function testValueIntersectionWillProduceASet()
72 | {
73 | $a = new \stdClass();
74 | $a->val = 'a';
75 | $b = new \stdClass();
76 | $b->val = 'b';
77 | $c = new \stdClass();
78 | $c->val = 'c';
79 |
80 | $setA = new Set([$a, $b]);
81 | $setB = new Set([$a, $c]);
82 |
83 | $test = array_map(
84 | function ($v) {
85 | return $v->val;
86 | },
87 | $setA->vIntersect($setB)->toArray()
88 | );
89 | $this->assertEquals(['a'], $test);
90 | }
91 |
92 | public function testValueUnionWillProduceASet()
93 | {
94 | $a = new \stdClass();
95 | $a->val = 'a';
96 | $b = new \stdClass();
97 | $b->val = 'b';
98 | $c = new \stdClass();
99 | $c->val = 'c';
100 |
101 | $setA = new Set([$a, $b]);
102 | $setB = new Set([$a, $c]);
103 |
104 | $test = array_map(
105 | function ($v) {
106 | return $v->val;
107 | },
108 | $setA->vUnion($setB)->toArray()
109 | );
110 | $this->assertEquals(['a', 'b', 'c'], $test);
111 | }
112 |
113 | public function testDiffWillProduceASet()
114 | {
115 | $a = new \stdClass();
116 | $a->val = 'a';
117 | $b = new \stdClass();
118 | $b->val = 'b';
119 | $c = new \stdClass();
120 | $c->val = 'c';
121 |
122 | $setA = new Set([$a, $b]);
123 | $setB = new Set([$a, $c]);
124 |
125 | $test = array_map(
126 | function ($v) {
127 | return $v->val;
128 | },
129 | $setA->diff($setB)->toArray()
130 | );
131 |
132 | $this->assertEquals(['b'], $test);
133 | }
134 |
135 | public function testYouCanBindAFunctionToTheEntireSetAndReturnASet()
136 | {
137 | $sut = Set::create([2, 3, 4, 5, 6]);
138 | //function returns a single value - converted to a collection
139 | $f = function ($c) {
140 | return $c[0];
141 | };
142 | $this->assertEquals([2], $sut->bind($f)->getArrayCopy());
143 | $this->assertInstanceOf('Monad\Set', $sut->bind($f));
144 | }
145 |
146 | public function testKintersectMethodIsNotSupportedForSets()
147 | {
148 | $this->expectException(\BadMethodCallException::class);
149 | $this->expectExceptionMessage('kIntersect is not a supported method for Sets');
150 | $setA = Set::create([2, 3, 4, 5, 6]);
151 | $setB = Set::create([2, 3, 4, 5, 6]);
152 | $setA->kIntersect($setB);
153 | }
154 |
155 | public function testKunionMethodIsNotSupportedForSets()
156 | {
157 | $this->expectException(\BadMethodCallException::class);
158 | $this->expectExceptionMessage('kUnion is not a supported method for Sets');
159 | $setA = Set::create([2, 3, 4, 5, 6]);
160 | $setB = Set::create([2, 3, 4, 5, 6]);
161 | $setA->kUnion($setB);
162 | }
163 |
164 | public function testKdiffMethodIsNotSupportedForSets()
165 | {
166 | $this->expectException(\BadMethodCallException::class);
167 | $this->expectExceptionMessage('kDiff is not a supported method for Sets');
168 | $setA = Set::create([2, 3, 4, 5, 6]);
169 | $setB = Set::create([2, 3, 4, 5, 6]);
170 | $setA->kDiff($setB);
171 | }
172 |
173 | }
174 |
--------------------------------------------------------------------------------