keep currying when compiling curried closures

This commit is contained in:
Kacper Donat 2026-10-28 09:56:11 +01:00
parent a4a51e32a1
commit 71e1b2cbdc
2 changed files with 23 additions and 1 deletions

View File

@ -71,6 +71,9 @@ function memoize(callable $callable)
* Flattens a callable into a plain closure. Compilable objects (decorators, pipelines, predicates) are compiled, so * 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(...)`); * 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(). * 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 function compile(callable $callable): \Closure
{ {
@ -78,7 +81,9 @@ function compile(callable $callable): \Closure
$reflection = new \ReflectionFunction($callable); $reflection = new \ReflectionFunction($callable);
$bound = $reflection->getClosureThis(); $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); return $callable instanceof Compilable ? $callable->compile() : closure($callable);

View File

@ -243,6 +243,23 @@ class CompileTest extends \PHPUnit\Framework\TestCase
$this->assertSame($pipeline('a')('b'), f\compile($pipeline)('a')('b')); $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() public function testCompileUnwrapsClosureOfCompilable()
{ {
$spy = new class(strtoupper(...)) extends FunctionPipeline { $spy = new class(strtoupper(...)) extends FunctionPipeline {