add currying
This commit is contained in:
parent
c9392f9b12
commit
f8eb8a0cf7
@ -10,6 +10,7 @@
|
||||
namespace Kadet\Functional\Reflection;
|
||||
|
||||
|
||||
use Kadet\Functional\Utils\CurriedFunction;
|
||||
use Kadet\Functional\Utils\PartiallyAppliedFunction;
|
||||
|
||||
class ReflectionPartiallyAppliedFunction extends \ReflectionFunction
|
||||
@ -55,4 +56,9 @@ class ReflectionPartiallyAppliedFunction extends \ReflectionFunction
|
||||
{
|
||||
return array_diff_key(parent::getParameters(), $this->applied->getArguments());
|
||||
}
|
||||
|
||||
public function isCurried()
|
||||
{
|
||||
return $this->applied instanceof CurriedFunction;
|
||||
}
|
||||
}
|
72
src/Utils/CurriedFunction.php
Normal file
72
src/Utils/CurriedFunction.php
Normal file
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2018 Kacper Donat
|
||||
*
|
||||
* @author Kacper "Kadet" Donat <kacper@kadet.net>
|
||||
*
|
||||
* Full license available in separate LICENSE file
|
||||
*/
|
||||
|
||||
namespace Kadet\Functional\Utils;
|
||||
|
||||
|
||||
use function Kadet\Functional\reflect;
|
||||
|
||||
class CurriedFunction extends PartiallyAppliedFunction
|
||||
{
|
||||
private $threshold;
|
||||
|
||||
/**
|
||||
* CurriedFunction constructor.
|
||||
*
|
||||
* @param callable $callable
|
||||
* @param int|null $threshold
|
||||
* @param array $args
|
||||
*/
|
||||
public function __construct(callable $callable, int $threshold = null, ...$args)
|
||||
{
|
||||
parent::__construct($callable, ...$args);
|
||||
$this->threshold = $threshold ?: reflect($callable)->getNumberOfRequiredParameters();
|
||||
}
|
||||
|
||||
public function getThreshold(): int
|
||||
{
|
||||
return $this->threshold;
|
||||
}
|
||||
|
||||
public function withThreshold(int $threshold = null)
|
||||
{
|
||||
return new static($this, $threshold);
|
||||
}
|
||||
|
||||
public function apply(...$args)
|
||||
{
|
||||
return new static($this, $this->threshold, ...$args);
|
||||
}
|
||||
|
||||
public function call(...$args)
|
||||
{
|
||||
return parent::__invoke(...$args);
|
||||
}
|
||||
|
||||
public function uncurry()
|
||||
{
|
||||
return new PartiallyAppliedFunction($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed ...$args
|
||||
*
|
||||
* @return \Kadet\Functional\Utils\CurriedFunction|mixed
|
||||
*/
|
||||
public function __invoke(...$args)
|
||||
{
|
||||
$applied = $this->apply(...$args);
|
||||
|
||||
if (count($applied->getArguments()) >= $this->threshold) {
|
||||
return $applied->call();
|
||||
}
|
||||
|
||||
return $applied;
|
||||
}
|
||||
}
|
@ -44,7 +44,7 @@ class PartiallyAppliedFunction implements Decorator
|
||||
return ($this->callable)(...$arguments);
|
||||
}
|
||||
|
||||
private function prepareArguments($args)
|
||||
protected function prepareArguments($args)
|
||||
{
|
||||
$result = [];
|
||||
foreach ($this->arguments as $argument) {
|
||||
|
@ -9,9 +9,20 @@
|
||||
|
||||
namespace Kadet\Functional;
|
||||
|
||||
use Kadet\Functional\Utils\CurriedFunction;
|
||||
use Kadet\Functional\Utils\PartiallyAppliedFunction;
|
||||
|
||||
function partial(callable $callable, ...$args)
|
||||
function partial(callable $callable, ...$args): PartiallyAppliedFunction
|
||||
{
|
||||
return new PartiallyAppliedFunction($callable, ...$args);
|
||||
}
|
||||
|
||||
function curry(callable $callable, int $threshold = null, ...$args): CurriedFunction
|
||||
{
|
||||
return new CurriedFunction($callable, $threshold, ...$args);
|
||||
}
|
||||
|
||||
function uncurry(CurriedFunction $curried)
|
||||
{
|
||||
return $curried->uncurry();
|
||||
}
|
@ -6,6 +6,8 @@ use Kadet\Functional\Predicate\AllOfPredicate;
|
||||
use Kadet\Functional\Predicate\AnyOfPredicate;
|
||||
use Kadet\Functional\Predicate\ClosurePredicate;
|
||||
use Kadet\Functional\Predicate\ConstantPredicate;
|
||||
use Kadet\Functional\Reflection\ReflectionPartiallyAppliedFunction;
|
||||
use Kadet\Functional\Utils\PartiallyAppliedFunction;
|
||||
|
||||
require_once __DIR__.'/Utils/index.php';
|
||||
|
||||
@ -66,4 +68,14 @@ function symbol($name = null)
|
||||
return sprintf("%s(%s)", $name ?: 'symbol', $id);
|
||||
}
|
||||
|
||||
function reflect(callable $callable): \ReflectionFunctionAbstract
|
||||
{
|
||||
switch (true) {
|
||||
case $callable instanceof PartiallyAppliedFunction:
|
||||
return new ReflectionPartiallyAppliedFunction($callable);
|
||||
default:
|
||||
return new \ReflectionFunction($callable);
|
||||
}
|
||||
}
|
||||
|
||||
define(__NAMESPACE__.'\\_', symbol());
|
159
tests/CurriedFunctionTest.php
Normal file
159
tests/CurriedFunctionTest.php
Normal file
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright 2018 Kacper Donat
|
||||
*
|
||||
* @author Kacper "Kadet" Donat <kacper@kadet.net>
|
||||
*
|
||||
* Full license available in separate LICENSE file
|
||||
*/
|
||||
|
||||
use Kadet\Functional as f;
|
||||
use const Kadet\Functional\_;
|
||||
|
||||
class CurriedFunctionTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var \Kadet\Functional\Utils\CurriedFunction
|
||||
*/
|
||||
private $function;
|
||||
private $variadic;
|
||||
private $optional;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
$this->function = function ($a, $b, $c, $d) {
|
||||
return $a . $b . $c . $d;
|
||||
};
|
||||
|
||||
$this->variadic = function (...$args) {
|
||||
return implode('', $args);
|
||||
};
|
||||
|
||||
$this->optional = function($a, $b = 'b', $c = 'c') {
|
||||
return $a . $b . $c;
|
||||
};
|
||||
}
|
||||
|
||||
public function testCallAfterAllArguments()
|
||||
{
|
||||
$f = f\curry($this->function);
|
||||
|
||||
$this->assertSame('abcd', $f('a')('b')('c')('d'));
|
||||
$this->assertSame('abcd', $f('a')('b', 'c')('d'));
|
||||
$this->assertSame('abcd', $f('a')('b', 'c', 'd'));
|
||||
$this->assertSame('abcd', $f('a', 'b')('c', 'd'));
|
||||
$this->assertSame('abcd', $f('a', 'b', 'c')('d'));
|
||||
$this->assertSame('abcd', $f('a', 'b', 'c', 'd'));
|
||||
}
|
||||
|
||||
public function testPartialApplication()
|
||||
{
|
||||
$f = f\curry($this->function);
|
||||
$g = $f('a', 'b');
|
||||
|
||||
$this->assertSame(['a', 'b'], $g->getArguments());
|
||||
}
|
||||
|
||||
public function testPartialApplicationWithPlaceholder()
|
||||
{
|
||||
$f = f\curry($this->function);
|
||||
$g = $f('a', _, 'c');
|
||||
|
||||
$this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g);
|
||||
$this->assertSame([0 => 'a', 2 => 'c'], $g->getArguments());
|
||||
$this->assertSame('abcd', $g('b', 'd'));
|
||||
}
|
||||
|
||||
public function testOptionalArgumentsCall()
|
||||
{
|
||||
$f = f\curry($this->optional);
|
||||
|
||||
$this->assertSame('abc', $f('a'));
|
||||
$this->assertSame('aBc', $f('a', 'B'));
|
||||
$this->assertSame('aBC', $f('a', 'B', 'C'));
|
||||
}
|
||||
|
||||
public function testVariadicThreshold()
|
||||
{
|
||||
$f = f\curry($this->variadic, 3);
|
||||
$g = $f('a', _, 'c');
|
||||
|
||||
$this->assertSame('abc', $f('a', 'b', 'c'));
|
||||
|
||||
$this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g);
|
||||
$this->assertSame([0 => 'a', 2 => 'c'], $g->getArguments());
|
||||
$this->assertSame('abc', $g('b'));
|
||||
}
|
||||
|
||||
public function testForceApply()
|
||||
{
|
||||
$f = f\curry($this->variadic);
|
||||
$g = $f->apply('a', 'b');
|
||||
|
||||
$this->assertSame('', $f());
|
||||
$this->assertSame('abc', $f('a', 'b', 'c'));
|
||||
|
||||
$this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g);
|
||||
$this->assertSame(['a', 'b'], $g->getArguments());
|
||||
$this->assertSame('abc', $g('c'));
|
||||
}
|
||||
|
||||
public function testForceCall()
|
||||
{
|
||||
$f = f\curry($this->variadic, 10);
|
||||
$g = $f->apply('a', 'b');
|
||||
|
||||
$this->assertSame('', $f->call());
|
||||
$this->assertSame('abc', $f->call('a', 'b', 'c'));
|
||||
|
||||
$this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g);
|
||||
$this->assertSame(['a', 'b'], $g->getArguments());
|
||||
$this->assertSame('abc', $g->call('c'));
|
||||
}
|
||||
|
||||
public function testUncurry()
|
||||
{
|
||||
$f = f\curry($this->variadic, 10);
|
||||
$f = $f('a', 'b');
|
||||
$f = $f->uncurry();
|
||||
|
||||
$this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $f);
|
||||
$this->assertSame(['a', 'b'], $f->getArguments());
|
||||
$this->assertSame('abc', $f('c'));
|
||||
}
|
||||
|
||||
public function testUncurryAlias()
|
||||
{
|
||||
$f = f\curry($this->variadic, 10);
|
||||
$f = $f('a', 'b');
|
||||
$f = f\uncurry($f);
|
||||
|
||||
$this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $f);
|
||||
$this->assertSame(['a', 'b'], $f->getArguments());
|
||||
$this->assertSame('abc', $f('c'));
|
||||
}
|
||||
|
||||
public function testThresholdChange()
|
||||
{
|
||||
$f = f\curry($this->variadic, 10);
|
||||
$g = $f->apply('a', 'b')->withThreshold(4);
|
||||
|
||||
$this->assertSame(4, $g->getThreshold());
|
||||
|
||||
$this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g);
|
||||
$this->assertInstanceOf(f\Utils\PartiallyAppliedFunction::class, $g('c'));
|
||||
$this->assertSame('abcd', $g('c', 'd'));
|
||||
}
|
||||
|
||||
public function testThreshold()
|
||||
{
|
||||
$f = f\curry($this->function);
|
||||
$this->assertSame(4, $f->getThreshold());
|
||||
|
||||
$g = f\curry($this->optional);
|
||||
$this->assertSame(1, $g->getThreshold());
|
||||
|
||||
$h = f\curry($this->variadic);
|
||||
$this->assertSame(0, $h->getThreshold());
|
||||
}
|
||||
}
|
@ -15,6 +15,9 @@ use Kadet\Functional\Reflection\ReflectionPartiallyAppliedFunction;
|
||||
|
||||
class ReflectionPartialAppliedFunctionTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var \Kadet\Functional\Utils\PartiallyAppliedFunction
|
||||
*/
|
||||
private $function;
|
||||
|
||||
protected function setUp()
|
||||
@ -67,4 +70,13 @@ class ReflectionPartialAppliedFunctionTest extends \PHPUnit\Framework\TestCase
|
||||
return $parameter->getValue();
|
||||
}, $parameters));
|
||||
}
|
||||
|
||||
public function testIsCurried()
|
||||
{
|
||||
$reflection = new ReflectionPartiallyAppliedFunction($this->function);
|
||||
$this->assertFalse($reflection->isCurried());
|
||||
|
||||
$curried = new ReflectionPartiallyAppliedFunction(f\curry($this->function->getDecorated()));
|
||||
$this->assertTrue($curried->isCurried());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user