diff --git a/composer.json b/composer.json index 8ecafd9c..7f1024d9 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,8 @@ "src/Functional/Average.php", "src/Functional/Capture.php", "src/Functional/ConstFunction.php", + "src/Functional/CompareOn.php", + "src/Functional/CompareObjectHashOn.php", "src/Functional/Compose.php", "src/Functional/Contains.php", "src/Functional/Difference.php", @@ -48,6 +50,7 @@ "src/Functional/InvokeFirst.php", "src/Functional/InvokeIf.php", "src/Functional/InvokeLast.php", + "src/Functional/Invoker.php", "src/Functional/Last.php", "src/Functional/LastIndexOf.php", "src/Functional/Map.php", diff --git a/docs/00-index.md b/docs/00-index.md index 02c4e31e..e833a319 100644 --- a/docs/00-index.md +++ b/docs/00-index.md @@ -5,7 +5,8 @@ - [Chapter 4: function functions](04-function-functions.md) - [Chapter 5: mathematical functions](05-mathematical-functions.md) - [Chapter 6: transformation functions](06-transformation-functions.md) - - [Chapter 7: miscellaneous](07-miscellaneous.md) + - [Chapter 7: comparator](07-comparator.md) + - [Chapter 8: miscellaneous](08-miscellaneous.md) # Functional PHP diff --git a/docs/01-list-comprehension.md b/docs/01-list-comprehension.md index 2dcd4dce..b3d56863 100644 --- a/docs/01-list-comprehension.md +++ b/docs/01-list-comprehension.md @@ -5,7 +5,8 @@ - [Chapter 4: function functions](04-function-functions.md) - [Chapter 5: mathematical functions](05-mathematical-functions.md) - [Chapter 6: transformation functions](06-transformation-functions.md) - - [Chapter 7: miscellaneous](07-miscellaneous.md) + - [Chapter 7: comparator](07-comparator.md) + - [Chapter 8: miscellaneous](08-miscellaneous.md) # Function overview diff --git a/docs/02-partial-application.md b/docs/02-partial-application.md index 53d913fe..eab349a9 100644 --- a/docs/02-partial-application.md +++ b/docs/02-partial-application.md @@ -5,7 +5,8 @@ - [Chapter 4: function functions](04-function-functions.md) - [Chapter 5: mathematical functions](05-mathematical-functions.md) - [Chapter 6: transformation functions](06-transformation-functions.md) - - [Chapter 7: miscellaneous](07-miscellaneous.md) + - [Chapter 7: comparator](07-comparator.md) + - [Chapter 8: miscellaneous](08-miscellaneous.md) # Partial application diff --git a/docs/03-access-functions.md b/docs/03-access-functions.md index 59d5e380..4eae3811 100644 --- a/docs/03-access-functions.md +++ b/docs/03-access-functions.md @@ -5,7 +5,8 @@ - [Chapter 4: function functions](04-function-functions.md) - [Chapter 5: mathematical functions](05-mathematical-functions.md) - [Chapter 6: transformation functions](06-transformation-functions.md) - - [Chapter 7: miscellaneous](07-miscellaneous.md) + - [Chapter 7: comparator](07-comparator.md) + - [Chapter 8: miscellaneous](08-miscellaneous.md) # Access functions @@ -55,6 +56,11 @@ Invokes method `$methodName` on the first object in the `$collection` and return Invokes method `$methodName` on the last object in the `$collection` and returns the results of the call +## invoker() + +``callable Functional\invoker(string $method[, array $methodArguments])`` +Returns a function that invokes method `$method` with arguments `$methodArguments` on the object + ## pluck() Fetch a single property from a collection of objects or arrays. diff --git a/docs/04-function-functions.md b/docs/04-function-functions.md index c0ac8dee..abb17b9f 100644 --- a/docs/04-function-functions.md +++ b/docs/04-function-functions.md @@ -5,7 +5,8 @@ - Chapter 4: function functions - [Chapter 5: mathematical functions](05-mathematical-functions.md) - [Chapter 6: transformation functions](06-transformation-functions.md) - - [Chapter 7: miscellaneous](07-miscellaneous.md) + - [Chapter 7: comparator](07-comparator.md) + - [Chapter 8: miscellaneous](08-miscellaneous.md) # Function functions diff --git a/docs/05-mathematical-functions.md b/docs/05-mathematical-functions.md index d6115083..920c03d1 100644 --- a/docs/05-mathematical-functions.md +++ b/docs/05-mathematical-functions.md @@ -5,7 +5,8 @@ - [Chapter 4: function functions](04-function-functions.md) - Chapter 5: mathematical functions - [Chapter 6: transformation functions](06-transformation-functions.md) - - [Chapter 7: miscellaneous](07-miscellaneous.md) + - [Chapter 7: comparator](07-comparator.md) + - [Chapter 8: miscellaneous](08-miscellaneous.md) # Mathematical functions diff --git a/docs/06-transformation-functions.md b/docs/06-transformation-functions.md index 54d7d2e4..524a4713 100644 --- a/docs/06-transformation-functions.md +++ b/docs/06-transformation-functions.md @@ -5,7 +5,8 @@ - [Chapter 4: function functions](04-function-functions.md) - [Chapter 5: mathematical functions](05-mathematical-functions.md) - Chapter 6: transformation functions - - [Chapter 7: miscellaneous](07-miscellaneous.md) + - [Chapter 7: comparator](07-comparator.md) + - [Chapter 8: miscellaneous](08-miscellaneous.md) # Transformation functions diff --git a/docs/07-comparator.md b/docs/07-comparator.md new file mode 100644 index 00000000..63b871ee --- /dev/null +++ b/docs/07-comparator.md @@ -0,0 +1,24 @@ + - [Overview](00-index.md) + - [Chapter 1: list comprehension](01-list-comprehension.md) + - [Chapter 2: partial application](02-partial-application.md) + - [Chapter 3: access functions](03-access-functions.md) + - [Chapter 4: function functions](04-function-functions.md) + - [Chapter 5: mathematical functions](05-mathematical-functions.md) + - [Chapter 6: transformation functions](06-transformation-functions.md) + - Chapter 7: comparator + - [Chapter 8: miscellaneous](08-miscellaneous.md) + +# Higher order comparison functions + +## compare_on & compare_object_hash_on + +``callable compare_on(callable $comparison; callable $keyFunction = Functional\const_function)`` +Returns a compare function that can be used with e.g. `usort()`, `array_udiff`, `array_uintersect` and so on. Takes a +comparison function as the first argument, pick e.g. `strcmp`, `strnatcmp` or `strnatcasecmp`. Second argument can be a +key function that is applied to both parameters passed to the compare function. + +``callable compare_object_hash_on(callable $comparison = 'strnatcasecmp', callable $keyFunction = 'Functional\const_function')`` +Returns a compare function function that expects `$left` and `$right` to be an object and compares them using the value +of `spl_object_hash`. First argument is the comparison function, pick e.g. `strcmp`, `strnatcmp` or `strnatcasecmp`. +Takes a key function as an optional argument that is invoked on both parameters passed to the compare function. It is +just a shortcut to `compare_on` as it composes the given key function with `spl_object_hash()` as a key function. diff --git a/docs/07-miscellaneous.md b/docs/08-miscellaneous.md similarity index 91% rename from docs/07-miscellaneous.md rename to docs/08-miscellaneous.md index d5d77e15..ac35ce9a 100644 --- a/docs/07-miscellaneous.md +++ b/docs/08-miscellaneous.md @@ -5,7 +5,8 @@ - [Chapter 4: function functions](04-function-functions.md) - [Chapter 5: mathematical functions](05-mathematical-functions.md) - [Chapter 6: transformation functions](06-transformation-functions.md) - - Chapter 7: miscellaneous + - [Chapter 7: comparator](07-comparator.md) + - Chapter 8: miscellaneous # Miscellaneous diff --git a/src/Functional/CompareObjectHashOn.php b/src/Functional/CompareObjectHashOn.php new file mode 100644 index 00000000..e3237583 --- /dev/null +++ b/src/Functional/CompareObjectHashOn.php @@ -0,0 +1,40 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +namespace Functional; + +use function Functional\compose; + +/** + * Returns a comparison function that can be used with e.g. `usort()` + * + * @param callable $comparison A function that compares the two values. Pick e.g. strcmp() or strnatcasecmp() + * @param callable $keyFunction A function that takes an argument and returns the value that should be compared + * @return callable + */ +function compare_object_hash_on(callable $comparison, callable $keyFunction = null) +{ + $keyFunction = $keyFunction ? compose($keyFunction, 'spl_object_hash') : 'spl_object_hash'; + + return compare_on($comparison, $keyFunction); +} + diff --git a/src/Functional/CompareOn.php b/src/Functional/CompareOn.php new file mode 100644 index 00000000..de17e69b --- /dev/null +++ b/src/Functional/CompareOn.php @@ -0,0 +1,43 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +namespace Functional; +/** + * Returns a comparison function that can be used with e.g. `usort()` + * + * @param callable $comparison A function that compares the two values. Pick e.g. strcmp() or strnatcasecmp() + * @param callable $reducer A function that takes an argument and returns the value that should be compared + * @return callable + */ +function compare_on(callable $comparison, callable $reducer = null) +{ + if ($reducer === null) { + return static function ($left, $right) use ($comparison) { + return $comparison($left, $right); + }; + } + + return static function ($left, $right) use ($reducer, $comparison) { + return $comparison($reducer($left), $reducer($right)); + }; +} + diff --git a/src/Functional/Invoker.php b/src/Functional/Invoker.php new file mode 100644 index 00000000..15a35a23 --- /dev/null +++ b/src/Functional/Invoker.php @@ -0,0 +1,42 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +namespace Functional; + +use Traversable; +use Functional\Exceptions\InvalidArgumentException; + +/** + * Returns a function that invokes method `$method` with arguments `$methodArguments` on the object + * + * @param string $methodName + * @param array $arguments + * @return callable + */ +function invoker($methodName, array $arguments = []) +{ + InvalidArgumentException::assertMethodName($methodName, __FUNCTION__, 1); + + return static function ($object) use ($methodName, $arguments) { + return $object->{$methodName}(...$arguments); + }; +} diff --git a/tests/Functional/CompareObjectHashOnTest.php b/tests/Functional/CompareObjectHashOnTest.php new file mode 100644 index 00000000..47201d05 --- /dev/null +++ b/tests/Functional/CompareObjectHashOnTest.php @@ -0,0 +1,45 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +namespace Functional\Tests; + +use function Functional\compare_object_hash_on; +use function Functional\const_function; +use stdClass; + +class CompareObjectHashOnTest extends AbstractTestCase +{ + public function testCompareValues() + { + $compare = compare_object_hash_on('strcmp'); + + $this->assertSame(0, $compare($this, $this)); + $this->assertNotSame(0, $compare($this, new stdClass())); + } + + public function testCompareWithReducer() + { + $compare = compare_object_hash_on('strcmp', const_function(new stdClass())); + + $this->assertSame(0, $compare($this, new stdClass())); + } +} diff --git a/tests/Functional/CompareOnTest.php b/tests/Functional/CompareOnTest.php new file mode 100644 index 00000000..e0d3be35 --- /dev/null +++ b/tests/Functional/CompareOnTest.php @@ -0,0 +1,45 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +namespace Functional\Tests; + +use function Functional\compare_on; +use function Functional\const_function; + +class CompareOnTest extends AbstractTestCase +{ + public function testCompareValues() + { + $comparator = compare_on('strcmp'); + + $this->assertSame(-1, $comparator(1, 2)); + $this->assertSame(0, $comparator(2, 2)); + $this->assertSame(1, $comparator(20, 10)); + } + + public function testCompareWithReducer() + { + $comparator = compare_on('strcmp', const_function(1)); + + $this->assertSame(0, $comparator(0, 1)); + } +} diff --git a/tests/Functional/InvokerTest.php b/tests/Functional/InvokerTest.php new file mode 100644 index 00000000..06253c40 --- /dev/null +++ b/tests/Functional/InvokerTest.php @@ -0,0 +1,73 @@ + + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +namespace Functional\Tests; + +use function Functional\invoker; + +class InvokerTest extends AbstractTestCase +{ + public function testInvokerWithoutArguments() + { + $fn = invoker('valueMethod'); + $this->assertSame('value', $fn($this)); + } + + public function testInvokerWithArguments() + { + $arguments = [1, 2, 3]; + $fn = invoker('argumentMethod', $arguments); + $this->assertSame($arguments, $fn($this)); + } + + public function testPassNoString() + { + $this->expectArgumentError('Functional\invoker() expects parameter 1 to be string'); + invoker([]); + } + + public function testInvalidMethod() + { + if (!class_exists('Error')) { + $this->markTestSkipped('Requires PHP 7'); + } + + $fn = invoker('undefinedMethod'); + + $this->expectException('Error', 'Call to undefined method Functional\\Tests\\InvokerTest::undefinedMethod'); + $fn($this); + } + + public function valueMethod(...$arguments) + { + $this->assertEmpty($arguments); + + return 'value'; + } + + public function argumentMethod(...$arguments) + { + $this->assertNotEmpty($arguments); + + return $arguments; + } +}