diff --git a/bin/psysh b/bin/psysh new file mode 100644 index 0000000..745beb0 --- /dev/null +++ b/bin/psysh @@ -0,0 +1,8 @@ +#!/usr/bin/env php + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional\Utils; + + +use Kadet\Functional\Decorator; + +class MemoizedFunction implements Decorator +{ + private $decorated; + private $memoized = []; + + public function __construct(callable $decorated) + { + $this->decorated = $decorated; + } + + public function getDecorated(): callable + { + return $this->decorated; + } + + public function __invoke(...$args) + { + $key = hash('sha256', print_r($args, true)); + + if (!array_key_exists($key, $this->memoized)) { + $this->memoized[$key] = ($this->decorated)(...$args); + } + + return $this->memoized[$key]; + } +} \ No newline at end of file diff --git a/src/Utils/index.php b/src/Utils/index.php index df42adb..72ca5be 100644 --- a/src/Utils/index.php +++ b/src/Utils/index.php @@ -12,6 +12,7 @@ namespace Kadet\Functional; use Kadet\Functional\Utils\CurriedFunction; use Kadet\Functional\Utils\FixedFunction; use Kadet\Functional\Utils\FunctionPipeline; +use Kadet\Functional\Utils\MemoizedFunction; use Kadet\Functional\Utils\PartiallyAppliedFunction; function partial(callable $callable, ...$args): PartiallyAppliedFunction @@ -66,3 +67,8 @@ function fix(callable $callable, ...$args) $f = new FixedFunction($callable); return $args ? apply($f, ...$args) : $f; } + +function memoize(callable $callable) +{ + return new MemoizedFunction($callable); +} \ No newline at end of file diff --git a/src/functions.php b/src/functions.php index 6986946..7fffd87 100644 --- a/src/functions.php +++ b/src/functions.php @@ -2,10 +2,12 @@ namespace Kadet\Functional; +use function foo\func; use Kadet\Functional\Predicate\AllOfPredicate; use Kadet\Functional\Predicate\AnyOfPredicate; use Kadet\Functional\Predicate\ClosurePredicate; use Kadet\Functional\Predicate\ConstantPredicate; +use Kadet\Functional\Reflection\ReflectionDecoratedFunction; use Kadet\Functional\Reflection\ReflectionFixedFunction; use Kadet\Functional\Reflection\ReflectionPartiallyAppliedFunction; use Kadet\Functional\Utils\FixedFunction; @@ -70,6 +72,13 @@ function symbol($name = null) return sprintf("%s(%s)", $name ?: 'symbol', $id); } +function repeat($times, callable $callback) +{ + for ($i = 0; $i < $times; $i++) { + $callback($times); + } +} + /** * @param callable $callable * @@ -83,6 +92,8 @@ function reflect(callable $callable): \ReflectionFunction return new ReflectionPartiallyAppliedFunction($callable); case $callable instanceof FixedFunction: return new ReflectionFixedFunction($callable); + case $callable instanceof Decorator: + return new ReflectionDecoratedFunction($callable->getDecorated()); default: return new \ReflectionFunction($callable); } diff --git a/tests/MemoizedFunctionTest.php b/tests/MemoizedFunctionTest.php new file mode 100644 index 0000000..ee5e174 --- /dev/null +++ b/tests/MemoizedFunctionTest.php @@ -0,0 +1,50 @@ + + * + * Full license available in separate LICENSE file + */ + +use Kadet\Functional as f; + +class MemoizedFunctionTest extends \PHPUnit\Framework\TestCase +{ + /** @var \PHPUnit\Framework\MockObject\MockObject */ + private $mock; + + protected function setUp()/* The :void return type declaration that should be here would cause a BC issue */ + { + $this->mock = $this + ->getMockBuilder('stdClass') + ->setMethods(['memoized']) + ->getMock(); + } + + public function testCalledOnlyOnce() + { + $memoized = f\memoize([$this->mock, 'memoized']); + + $this->mock->expects($this->once())->method('memoized')->with(); + + for ($i = 0; $i < 10; $i++) { + $memoized(); + } + } + + public function testCalledOnlyOncePerArgs() + { + $memoized = f\memoize([$this->mock, 'memoized']); + + $this->mock->expects($this->exactly(2))->method('memoized')->withConsecutive( + [ 'a' ], + [ [] ] + ); + + for ($i = 0; $i < 10; $i++) { + $memoized('a'); + $memoized([]); + } + } +} \ No newline at end of file