From 714160c76463d462162121cb5a823e52a9672506 Mon Sep 17 00:00:00 2001 From: An Phan Date: Sat, 21 Mar 2026 19:55:58 +0100 Subject: [PATCH] Add `juxt`, inspired by Clojure's `juxt` Co-Authored-By: Claude Opus 4.6 (1M context) --- composer.json | 1 + docs/functional-php.md | 17 +++++++++ src/Functional/Functional.php | 5 +++ src/Functional/Juxt.php | 32 +++++++++++++++++ tests/Functional/JuxtTest.php | 68 +++++++++++++++++++++++++++++++++++ 5 files changed, 123 insertions(+) create mode 100644 src/Functional/Juxt.php create mode 100644 tests/Functional/JuxtTest.php diff --git a/composer.json b/composer.json index 905624ab..b84ab0da 100644 --- a/composer.json +++ b/composer.json @@ -74,6 +74,7 @@ "src/Functional/InvokeIf.php", "src/Functional/InvokeLast.php", "src/Functional/Invoker.php", + "src/Functional/Juxt.php", "src/Functional/Last.php", "src/Functional/LastIndexOf.php", "src/Functional/LessThan.php", diff --git a/docs/functional-php.md b/docs/functional-php.md index 62134233..4cf3fafa 100644 --- a/docs/functional-php.md +++ b/docs/functional-php.md @@ -329,6 +329,23 @@ use function Functional\partial_method; $registeredUsers = select($users, partial_method('isRegistered')); ``` +## juxt() +Takes a list of functions and returns a new function that applies each function to the given arguments and returns an array of the results. Inspired by Clojure's `juxt`. + +``callable Functional\juxt(callable ...$functions)`` + +```php +use function Functional\juxt; + +$getStats = juxt('min', 'max'); +$getStats(3, 1, 5, 2); // [1, 5] + +$transform = juxt('strtoupper', 'strtolower', 'strrev'); +$transform('Hello'); // ['HELLO', 'hello', 'olleH'] +``` + +_See also `converge()`, which additionally passes the results through a converging function._ + ## converge() ``callable Functional\converge(callable $convergingFunction, callable[] branchingFunctions)`` diff --git a/src/Functional/Functional.php b/src/Functional/Functional.php index 58784708..b57edf9f 100644 --- a/src/Functional/Functional.php +++ b/src/Functional/Functional.php @@ -237,6 +237,11 @@ final class Functional */ const invoker = '\Functional\invoker'; + /** + * @see \Functional\juxt + */ + const juxt = '\Functional\juxt'; + /** * @see \Functional\last */ diff --git a/src/Functional/Juxt.php b/src/Functional/Juxt.php new file mode 100644 index 00000000..8a36b0aa --- /dev/null +++ b/src/Functional/Juxt.php @@ -0,0 +1,32 @@ + + * @copyright 2011-2021 Lars Strojny + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/lstrojny/functional-php + */ + +namespace Functional; + +/** + * Takes a list of functions and returns a new function that applies each + * function to the given arguments and returns an array of the results. + * + * @param callable ...$functions + * @return callable + * @no-named-arguments + */ +function juxt(callable ...$functions) +{ + return function (...$values) use ($functions) { + $result = []; + + foreach ($functions as $function) { + $result[] = $function(...$values); + } + + return $result; + }; +} diff --git a/tests/Functional/JuxtTest.php b/tests/Functional/JuxtTest.php new file mode 100644 index 00000000..82e9d250 --- /dev/null +++ b/tests/Functional/JuxtTest.php @@ -0,0 +1,68 @@ + + * @copyright 2011-2021 Lars Strojny + * @license https://opensource.org/licenses/MIT MIT + * @link https://github.com/lstrojny/functional-php + */ + +namespace Functional\Tests; + +use function Functional\juxt; + +class JuxtTest extends AbstractTestCase +{ + public function test(): void + { + $juxted = juxt('strtoupper', 'strtolower', 'strrev'); + + self::assertSame(['HELLO', 'hello', 'olleH'], $juxted('Hello')); + } + + public function testWithMultipleArguments(): void + { + $minMax = juxt('min', 'max'); + + self::assertSame([1, 5], $minMax(3, 1, 5, 2)); + } + + public function testWithArrayFunctions(): void + { + $stats = juxt('array_sum', 'count'); + + self::assertSame([15, 5], $stats([1, 2, 3, 4, 5])); + } + + public function testWithClosures(): void + { + $double = static function ($x) { + return $x * 2; + }; + $square = static function ($x) { + return $x * $x; + }; + $negate = static function ($x) { + return -$x; + }; + + $juxted = juxt($double, $square, $negate); + + self::assertSame([10, 25, -5], $juxted(5)); + } + + public function testWithNoFunctions(): void + { + $juxted = juxt(); + + self::assertSame([], $juxted('anything')); + } + + public function testWithSingleFunction(): void + { + $juxted = juxt('strlen'); + + self::assertSame([5], $juxted('hello')); + } +}