diff --git a/phpunit.xml b/phpunit.xml index e69de29..98abe31 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -0,0 +1,11 @@ + + + + tests + + + + src + + + diff --git a/src/Utils/PartiallyAppliedFunction.php b/src/Utils/PartiallyAppliedFunction.php new file mode 100644 index 0000000..2376400 --- /dev/null +++ b/src/Utils/PartiallyAppliedFunction.php @@ -0,0 +1,53 @@ + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional\Utils; + +use const Kadet\Functional\_ as PLACEHOLDER; + +class PartiallyAppliedFunction +{ + protected $callable; + protected $arguments = []; + + public function __construct(callable $callable, ...$args) + { + switch (true) { + case $callable instanceof PartiallyAppliedFunction: + $this->callable = $callable->callable; + $this->arguments = $callable->prepareArguments($args); + break; + + default: + $this->callable = $callable; + $this->arguments = array_values($args); + } + } + + public function __invoke(...$args) + { + $arguments = $this->prepareArguments($args); + return ($this->callable)(...$arguments); + } + + private function prepareArguments($args) + { + $result = []; + foreach ($this->arguments as $argument) { + $result[] = $argument === PLACEHOLDER + ? array_shift($args) + : $argument; + } + + return array_merge( + array_values($result), + array_values($args) + ); + } +} \ No newline at end of file diff --git a/src/Utils/index.php b/src/Utils/index.php new file mode 100644 index 0000000..fde7190 --- /dev/null +++ b/src/Utils/index.php @@ -0,0 +1,17 @@ + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional; + +use Kadet\Functional\Utils\PartiallyAppliedFunction; + +function partial(callable $callable, ...$args) +{ + return new PartiallyAppliedFunction($callable, ...$args); +} diff --git a/src/functions.php b/src/functions.php index ba6f502..0e3988f 100644 --- a/src/functions.php +++ b/src/functions.php @@ -7,6 +7,8 @@ use Kadet\Functional\Predicate\AnyOfPredicate; use Kadet\Functional\Predicate\ClosurePredicate; use Kadet\Functional\Predicate\ConstantPredicate; +require_once __DIR__.'/Utils/index.php'; + /** * @param $predicate * @@ -57,3 +59,11 @@ function any(...$predicates) $predicates = unpack($predicates); return new AnyOfPredicate(...array_map(predicate::class, $predicates)); } + +function symbol($name = null) +{ + $id = random_bytes(64); + return sprintf("%s(%s)", $name ?: 'symbol', $id); +} + +define(__NAMESPACE__.'\\_', symbol()); \ No newline at end of file diff --git a/tests/PartialApplicationTest.php b/tests/PartialApplicationTest.php new file mode 100644 index 0000000..dacf4a4 --- /dev/null +++ b/tests/PartialApplicationTest.php @@ -0,0 +1,57 @@ + + * + * Full license available in separate LICENSE file + */ + +use \Kadet\Functional as f; +use const Kadet\Functional\_; + +function subtract($a, $b) { + return $a - $b; +} + +class PartialApplicationTest extends \PHPUnit\Framework\TestCase +{ + public function testPartialApplicationOfOneArgument() + { + $applied = f\partial('subtract', 10); + + $this->assertEquals(0, $applied(10)); + $this->assertEquals(10, $applied(0)); + $this->assertEquals(20, $applied(-10)); + } + + public function testPartialApplicationOfAllArguments() + { + $applied = f\partial('subtract', 10, 20); + + $this->assertEquals(-10, $applied()); + } + + public function testPartialApplicationOfFewArguments() + { + $applied = f\partial('array_merge', ['a'], ['b'], ['c']); + + $this->assertEquals(['a', 'b', 'c', 'd', 'e'], $applied(['d'], ['e'])); + } + + public function testPartialApplicationWithPlaceholders() + { + $applied = f\partial('array_merge', ['a'], _, ['c']); + + $this->assertEquals(['a', 'd', 'c'], $applied(['d'])); + } + + public function testPartialApplicationOfAppliedFunction() + { + + $applied = f\partial('array_merge', ['a'], _, ['c']); + $double = f\partial($applied, _, ['d']); + + $this->assertEquals(['a', 'b', 'c', 'd', 'e'], $double(['b'], ['e'])); + } +} \ No newline at end of file