From 71e1b2cbdc6656d59b180f70176b04efa0b9134a Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Wed, 28 Oct 2026 09:56:11 +0100 Subject: [PATCH] keep currying when compiling curried closures --- src/Utils/index.php | 7 ++++++- tests/CompileTest.php | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Utils/index.php b/src/Utils/index.php index b7ee398..aca3902 100644 --- a/src/Utils/index.php +++ b/src/Utils/index.php @@ -71,6 +71,9 @@ function memoize(callable $callable) * Flattens a callable into a plain closure. Compilable objects (decorators, pipelines, predicates) are compiled, so * calling the result does not go through them, also when given as a closure of their `__invoke` (e.g. `$pipe(...)`); * any other callable is converted with closure(). + * + * A closure of a curried function's `__invoke` (`$curried(...)`) is kept as it is, so it keeps currying; the compiled + * curried function itself calls its target and does not curry further. */ function compile(callable $callable): \Closure { @@ -78,7 +81,9 @@ function compile(callable $callable): \Closure $reflection = new \ReflectionFunction($callable); $bound = $reflection->getClosureThis(); - return $bound instanceof Compilable && $reflection->getName() === '__invoke' ? $bound->compile() : $callable; + return $bound instanceof Compilable && !$bound instanceof CurriedFunction && $reflection->getName() === '__invoke' + ? $bound->compile() + : $callable; } return $callable instanceof Compilable ? $callable->compile() : closure($callable); diff --git a/tests/CompileTest.php b/tests/CompileTest.php index 81345ad..d9b159a 100644 --- a/tests/CompileTest.php +++ b/tests/CompileTest.php @@ -243,6 +243,23 @@ class CompileTest extends \PHPUnit\Framework\TestCase $this->assertSame($pipeline('a')('b'), f\compile($pipeline)('a')('b')); } + public function testCompiledClosureOfCurriedKeepsCurrying() + { + $curried = f\curry(fn ($a, $b) => "$a$b"); + + $this->assertSame('ab', f\compile($curried(...))('a')('b')); + $this->assertSame('ab', f\compile($curried(...))('a', 'b')); + } + + public function testCompiledPipelineOfCurriedClosureKeepsCurrying() + { + $curried = f\curry(fn ($a, $b) => "$a$b"); + $pipeline = f\pipe(strtoupper(...), $curried(...)); + + $this->assertSame($pipeline('a')('b'), f\compile($pipeline)('a')('b')); + $this->assertSame('ab', f\compile(f\pipe($curried(...), fn ($rest) => $rest('b')))('a')); + } + public function testCompileUnwrapsClosureOfCompilable() { $spy = new class(strtoupper(...)) extends FunctionPipeline {