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;
|
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 Kadet\Functional\Predicate;
|
||||||
|
|
||||||
use function Kadet\Functional\Internal\callback;
|
use function Kadet\Functional\Internal\callback;
|
||||||
|
use function Kadet\Functional\Internal\isUnary;
|
||||||
|
|
||||||
class AllOfPredicate extends AbstractPredicate implements Compilable
|
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);
|
$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 {
|
return static function (mixed ...$args) use ($predicates): bool {
|
||||||
foreach ($predicates as $predicate) {
|
foreach ($predicates as $predicate) {
|
||||||
if (!$predicate(...$args)) {
|
if (!$predicate(...$args)) {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ use Kadet\Functional\Compilable;
|
|||||||
use Kadet\Functional\Predicate;
|
use Kadet\Functional\Predicate;
|
||||||
|
|
||||||
use function Kadet\Functional\Internal\callback;
|
use function Kadet\Functional\Internal\callback;
|
||||||
|
use function Kadet\Functional\Internal\isUnary;
|
||||||
|
|
||||||
class AnyOfPredicate extends AbstractPredicate implements Compilable
|
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);
|
$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 {
|
return static function (mixed ...$args) use ($predicates): bool {
|
||||||
foreach ($predicates as $predicate) {
|
foreach ($predicates as $predicate) {
|
||||||
if ($predicate(...$args)) {
|
if ($predicate(...$args)) {
|
||||||
|
|||||||
@ -14,6 +14,7 @@ use Kadet\Functional\Compilable;
|
|||||||
use Kadet\Functional\Decorator;
|
use Kadet\Functional\Decorator;
|
||||||
|
|
||||||
use function Kadet\Functional\compile;
|
use function Kadet\Functional\compile;
|
||||||
|
use function Kadet\Functional\Internal\isUnary;
|
||||||
|
|
||||||
class ClosurePredicate extends AbstractPredicate implements Decorator, Compilable
|
class ClosurePredicate extends AbstractPredicate implements Decorator, Compilable
|
||||||
{
|
{
|
||||||
@ -54,6 +55,8 @@ class ClosurePredicate extends AbstractPredicate implements Decorator, Compilabl
|
|||||||
return $compiled;
|
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 Kadet\Functional\Predicate;
|
||||||
|
|
||||||
use function Kadet\Functional\compile;
|
use function Kadet\Functional\compile;
|
||||||
|
use function Kadet\Functional\Internal\isUnary;
|
||||||
|
|
||||||
class NegatedPredicate extends AbstractPredicate implements Decorator, Compilable
|
class NegatedPredicate extends AbstractPredicate implements Decorator, Compilable
|
||||||
{
|
{
|
||||||
@ -49,6 +50,8 @@ class NegatedPredicate extends AbstractPredicate implements Decorator, Compilabl
|
|||||||
{
|
{
|
||||||
$negated = compile($this->negated);
|
$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 Kadet\Functional\Compilable;
|
||||||
|
|
||||||
use function Kadet\Functional\compile;
|
use function Kadet\Functional\compile;
|
||||||
|
use function Kadet\Functional\Internal\isUnary;
|
||||||
|
|
||||||
class FunctionPipeline implements Compilable
|
class FunctionPipeline implements Compilable
|
||||||
{
|
{
|
||||||
@ -82,13 +83,15 @@ class FunctionPipeline implements Compilable
|
|||||||
return $first;
|
return $first;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($functions) === 1) {
|
// A first function taking one argument ignores any other, so the compiled pipeline can take one as well.
|
||||||
$next = $functions[0];
|
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) {
|
return $result;
|
||||||
$result = $first(...$args);
|
|
||||||
|
|
||||||
return $result instanceof UnpackedArgs ? $next(...$result->getArrayCopy()) : $next($result);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -292,4 +292,25 @@ class CompileTest extends \PHPUnit\Framework\TestCase
|
|||||||
$this->assertFalse($compiled(2));
|
$this->assertFalse($compiled(2));
|
||||||
$this->assertSame(0, $inner->calls);
|
$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