compile one-argument pipelines and predicates without spreading
This commit is contained in:
parent
f3f05fd00c
commit
04dfb21916
@ -111,3 +111,20 @@ function target(callable $callable): callable
|
||||
return $callable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether calling a closure with one argument is the same as calling it with more: a userland, non-variadic closure
|
||||
* with one required parameter, as PHP drops the arguments past it. A closure wrapping it can then take one argument
|
||||
* as well, which is cheaper to call than a variadic one.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function isUnary(\Closure $closure): bool
|
||||
{
|
||||
$reflection = new \ReflectionFunction($closure);
|
||||
|
||||
return !$reflection->isInternal()
|
||||
&& !$reflection->isVariadic()
|
||||
&& $reflection->getNumberOfParameters() === 1
|
||||
&& $reflection->getNumberOfRequiredParameters() === 1;
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ use Kadet\Functional\Compilable;
|
||||
use Kadet\Functional\Predicate;
|
||||
|
||||
use function Kadet\Functional\Internal\callback;
|
||||
use function Kadet\Functional\Internal\isUnary;
|
||||
|
||||
class AllOfPredicate extends AbstractPredicate implements Compilable
|
||||
{
|
||||
@ -53,6 +54,18 @@ class AllOfPredicate extends AbstractPredicate implements Compilable
|
||||
{
|
||||
$predicates = array_map(static fn (Predicate $predicate) => callback($predicate), $this->predicates);
|
||||
|
||||
if ($predicates && count(array_filter($predicates, isUnary(...))) === count($predicates)) {
|
||||
return static function ($value) use ($predicates): bool {
|
||||
foreach ($predicates as $predicate) {
|
||||
if (!$predicate($value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
return static function (mixed ...$args) use ($predicates): bool {
|
||||
foreach ($predicates as $predicate) {
|
||||
if (!$predicate(...$args)) {
|
||||
|
||||
@ -14,6 +14,7 @@ use Kadet\Functional\Compilable;
|
||||
use Kadet\Functional\Predicate;
|
||||
|
||||
use function Kadet\Functional\Internal\callback;
|
||||
use function Kadet\Functional\Internal\isUnary;
|
||||
|
||||
class AnyOfPredicate extends AbstractPredicate implements Compilable
|
||||
{
|
||||
@ -54,6 +55,18 @@ class AnyOfPredicate extends AbstractPredicate implements Compilable
|
||||
{
|
||||
$predicates = array_map(static fn (Predicate $predicate) => callback($predicate), $this->predicates);
|
||||
|
||||
if ($predicates && count(array_filter($predicates, isUnary(...))) === count($predicates)) {
|
||||
return static function ($value) use ($predicates): bool {
|
||||
foreach ($predicates as $predicate) {
|
||||
if ($predicate($value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
return static function (mixed ...$args) use ($predicates): bool {
|
||||
foreach ($predicates as $predicate) {
|
||||
if ($predicate(...$args)) {
|
||||
|
||||
@ -14,6 +14,7 @@ use Kadet\Functional\Compilable;
|
||||
use Kadet\Functional\Decorator;
|
||||
|
||||
use function Kadet\Functional\compile;
|
||||
use function Kadet\Functional\Internal\isUnary;
|
||||
|
||||
class ClosurePredicate extends AbstractPredicate implements Decorator, Compilable
|
||||
{
|
||||
@ -54,6 +55,8 @@ class ClosurePredicate extends AbstractPredicate implements Decorator, Compilabl
|
||||
return $compiled;
|
||||
}
|
||||
|
||||
return static fn (mixed ...$args): bool => $compiled(...$args);
|
||||
return isUnary($compiled)
|
||||
? static fn ($value): bool => $compiled($value)
|
||||
: static fn (mixed ...$args): bool => $compiled(...$args);
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ use Kadet\Functional\Decorator;
|
||||
use Kadet\Functional\Predicate;
|
||||
|
||||
use function Kadet\Functional\compile;
|
||||
use function Kadet\Functional\Internal\isUnary;
|
||||
|
||||
class NegatedPredicate extends AbstractPredicate implements Decorator, Compilable
|
||||
{
|
||||
@ -49,6 +50,8 @@ class NegatedPredicate extends AbstractPredicate implements Decorator, Compilabl
|
||||
{
|
||||
$negated = compile($this->negated);
|
||||
|
||||
return static fn (mixed ...$args): bool => !$negated(...$args);
|
||||
return isUnary($negated)
|
||||
? static fn ($value): bool => !$negated($value)
|
||||
: static fn (mixed ...$args): bool => !$negated(...$args);
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,7 @@ namespace Kadet\Functional\Utils;
|
||||
use Kadet\Functional\Compilable;
|
||||
|
||||
use function Kadet\Functional\compile;
|
||||
use function Kadet\Functional\Internal\isUnary;
|
||||
|
||||
class FunctionPipeline implements Compilable
|
||||
{
|
||||
@ -82,13 +83,15 @@ class FunctionPipeline implements Compilable
|
||||
return $first;
|
||||
}
|
||||
|
||||
if (count($functions) === 1) {
|
||||
$next = $functions[0];
|
||||
// A first function taking one argument ignores any other, so the compiled pipeline can take one as well.
|
||||
if (isUnary($first)) {
|
||||
return static function ($value) use ($first, $functions) {
|
||||
$result = $first($value);
|
||||
foreach ($functions as $function) {
|
||||
$result = $result instanceof UnpackedArgs ? $function(...$result->getArrayCopy()) : $function($result);
|
||||
}
|
||||
|
||||
return static function (...$args) use ($first, $next) {
|
||||
$result = $first(...$args);
|
||||
|
||||
return $result instanceof UnpackedArgs ? $next(...$result->getArrayCopy()) : $next($result);
|
||||
return $result;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -292,4 +292,25 @@ class CompileTest extends \PHPUnit\Framework\TestCase
|
||||
$this->assertFalse($compiled(2));
|
||||
$this->assertSame(0, $inner->calls);
|
||||
}
|
||||
|
||||
public function testUnaryCompiledCallablesMatchWithExtraArguments()
|
||||
{
|
||||
$callables = [
|
||||
f\pipe(fn ($x) => $x, strtoupper(...)),
|
||||
f\predicate(fn ($x) => $x === 'a'),
|
||||
f\not(fn ($x) => $x === 'a'),
|
||||
f\all(fn ($x) => $x === 'a', p\same('a')),
|
||||
f\any(fn ($x) => $x === 'b', p\same('a')),
|
||||
];
|
||||
|
||||
foreach ($callables as $callable) {
|
||||
$this->assertSame($callable('a', 'extra'), f\compile($callable)('a', 'extra'));
|
||||
}
|
||||
}
|
||||
|
||||
public function testCompiledEmptyCombinatorsTakeNoArguments()
|
||||
{
|
||||
$this->assertSame(f\all()(), f\compile(f\all())());
|
||||
$this->assertSame(f\any()(), f\compile(f\any())());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user