diff --git a/composer.json b/composer.json index 7ae37f0..cadaaf8 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "php": ">=7.1" }, "require-dev": { - "phpunit/phpunit": "^7.0" + "phpunit/phpunit": "^7.0", + "psy/psysh": "@stable" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index dbcf712..04fdd6f 100644 --- a/composer.lock +++ b/composer.lock @@ -1,12 +1,45 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "351b2aa48831d572ce00eda2f6f5e205", + "content-hash": "871052e2126565e18c54e279dead3d57", "packages": [], "packages-dev": [ + { + "name": "dnoegel/php-xdg-base-dir", + "version": "0.1", + "source": { + "type": "git", + "url": "https://github.com/dnoegel/php-xdg-base-dir.git", + "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dnoegel/php-xdg-base-dir/zipball/265b8593498b997dc2d31e75b89f053b5cc9621a", + "reference": "265b8593498b997dc2d31e75b89f053b5cc9621a", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "@stable" + }, + "type": "project", + "autoload": { + "psr-4": { + "XdgBaseDir\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "implementation of xdg base directory specification for php", + "time": "2014-10-24T07:27:01+00:00" + }, { "name": "doctrine/instantiator", "version": "1.1.0", @@ -61,6 +94,93 @@ ], "time": "2017-07-22T11:58:36+00:00" }, + { + "name": "jakub-onderka/php-console-color", + "version": "0.1", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Color.git", + "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Color/zipball/e0b393dacf7703fc36a4efc3df1435485197e6c1", + "reference": "e0b393dacf7703fc36a4efc3df1435485197e6c1", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "jakub-onderka/php-code-style": "1.0", + "jakub-onderka/php-parallel-lint": "0.*", + "jakub-onderka/php-var-dump-check": "0.*", + "phpunit/phpunit": "3.7.*", + "squizlabs/php_codesniffer": "1.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "JakubOnderka\\PhpConsoleColor": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-2-Clause" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "jakub.onderka@gmail.com", + "homepage": "http://www.acci.cz" + } + ], + "time": "2014-04-08T15:00:19+00:00" + }, + { + "name": "jakub-onderka/php-console-highlighter", + "version": "v0.3.2", + "source": { + "type": "git", + "url": "https://github.com/JakubOnderka/PHP-Console-Highlighter.git", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/JakubOnderka/PHP-Console-Highlighter/zipball/7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "reference": "7daa75df45242c8d5b75a22c00a201e7954e4fb5", + "shasum": "" + }, + "require": { + "jakub-onderka/php-console-color": "~0.1", + "php": ">=5.3.0" + }, + "require-dev": { + "jakub-onderka/php-code-style": "~1.0", + "jakub-onderka/php-parallel-lint": "~0.5", + "jakub-onderka/php-var-dump-check": "~0.1", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~1.5" + }, + "type": "library", + "autoload": { + "psr-0": { + "JakubOnderka\\PhpConsoleHighlighter": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jakub Onderka", + "email": "acci@acci.cz", + "homepage": "http://www.acci.cz/" + } + ], + "time": "2015-04-20T18:58:01+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.7.0", @@ -106,6 +226,57 @@ ], "time": "2017-10-19T19:58:43+00:00" }, + { + "name": "nikic/php-parser", + "version": "v4.0.3", + "source": { + "type": "git", + "url": "https://github.com/nikic/PHP-Parser.git", + "reference": "bd088dc940a418f09cda079a9b5c7c478890fb8d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/bd088dc940a418f09cda079a9b5c7c478890fb8d", + "reference": "bd088dc940a418f09cda079a9b5c7c478890fb8d", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^6.5 || ^7.0" + }, + "bin": [ + "bin/php-parse" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "psr-4": { + "PhpParser\\": "lib/PhpParser" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Nikita Popov" + } + ], + "description": "A PHP parser written in PHP", + "keywords": [ + "parser", + "php" + ], + "time": "2018-07-15T17:25:16+00:00" + }, { "name": "phar-io/manifest", "version": "1.0.1", @@ -808,6 +979,80 @@ ], "time": "2018-02-15T05:27:38+00:00" }, + { + "name": "psy/psysh", + "version": "v0.9.7", + "source": { + "type": "git", + "url": "https://github.com/bobthecow/psysh.git", + "reference": "4f5b6c090948773a8bfeea6a0f07ab7d0b24e932" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/4f5b6c090948773a8bfeea6a0f07ab7d0b24e932", + "reference": "4f5b6c090948773a8bfeea6a0f07ab7d0b24e932", + "shasum": "" + }, + "require": { + "dnoegel/php-xdg-base-dir": "0.1", + "ext-json": "*", + "ext-tokenizer": "*", + "jakub-onderka/php-console-highlighter": "0.3.*", + "nikic/php-parser": "~1.3|~2.0|~3.0|~4.0", + "php": ">=5.4.0", + "symfony/console": "~2.3.10|^2.4.2|~3.0|~4.0", + "symfony/var-dumper": "~2.7|~3.0|~4.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.2", + "hoa/console": "~2.15|~3.16", + "phpunit/phpunit": "~4.8.35|~5.0|~6.0|~7.0" + }, + "suggest": { + "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", + "ext-pdo-sqlite": "The doc command requires SQLite to work.", + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", + "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history.", + "hoa/console": "A pure PHP readline implementation. You'll want this if your PHP install doesn't already support readline or libedit." + }, + "bin": [ + "bin/psysh" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-develop": "0.9.x-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Psy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Justin Hileman", + "email": "justin@justinhileman.info", + "homepage": "http://justinhileman.com" + } + ], + "description": "An interactive shell for modern PHP.", + "homepage": "http://psysh.org", + "keywords": [ + "REPL", + "console", + "interactive", + "shell" + ], + "time": "2018-08-11T15:54:43+00:00" + }, { "name": "sebastian/code-unit-reverse-lookup", "version": "1.0.1", @@ -1371,6 +1616,263 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, + { + "name": "symfony/console", + "version": "v4.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "ca80b8ced97cf07390078b29773dc384c39eee1f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/ca80b8ced97cf07390078b29773dc384c39eee1f", + "reference": "ca80b8ced97cf07390078b29773dc384c39eee1f", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/dependency-injection": "<3.4", + "symfony/process": "<3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~3.4|~4.0", + "symfony/dependency-injection": "~3.4|~4.0", + "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/lock": "~3.4|~4.0", + "symfony/process": "~3.4|~4.0" + }, + "suggest": { + "psr/log-implementation": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "https://symfony.com", + "time": "2018-07-26T11:24:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", + "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2018-08-06T14:22:27+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.9.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/95c50420b0baed23852452a7f0c7b527303ed5ae", + "reference": "95c50420b0baed23852452a7f0c7b527303ed5ae", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2018-08-06T14:22:27+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "69e174f4c02ec43919380171c6f7550753299316" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/69e174f4c02ec43919380171c6f7550753299316", + "reference": "69e174f4c02ec43919380171c6f7550753299316", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/process": "~3.4|~4.0", + "twig/twig": "~1.34|~2.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.1-dev" + } + }, + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "time": "2018-07-26T11:24:31+00:00" + }, { "name": "theseer/tokenizer", "version": "1.1.0", @@ -1464,7 +1966,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": { + "psy/psysh": 0 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/src/Reflection/ReflectionFixedFunction.php b/src/Reflection/ReflectionFixedFunction.php new file mode 100644 index 0000000..cc72f81 --- /dev/null +++ b/src/Reflection/ReflectionFixedFunction.php @@ -0,0 +1,21 @@ + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional\Reflection; + + +use Kadet\Functional\Utils\FixedFunction; + +class ReflectionFixedFunction extends \ReflectionFunction +{ + public function __construct(FixedFunction $function) + { + parent::__construct($function->getDecorated()($function)); + } +} \ No newline at end of file diff --git a/src/Utils/CurriedFunction.php b/src/Utils/CurriedFunction.php index db0b9ae..faa32a4 100644 --- a/src/Utils/CurriedFunction.php +++ b/src/Utils/CurriedFunction.php @@ -68,7 +68,6 @@ class CurriedFunction extends PartiallyAppliedFunction } $applied = $this->apply(...$args); - if (count($applied->getArguments()) >= $this->threshold) { return $applied->call(); } diff --git a/src/Utils/FixedFunction.php b/src/Utils/FixedFunction.php new file mode 100644 index 0000000..e86582d --- /dev/null +++ b/src/Utils/FixedFunction.php @@ -0,0 +1,42 @@ + + * + * Full license available in separate LICENSE file + */ + +namespace Kadet\Functional\Utils; + + +use function Kadet\Functional\apply; +use Kadet\Functional\Decorator; + +class FixedFunction implements Decorator +{ + /** + * @var callable + */ + private $fixed; + + /** + * FixedFunction constructor. + * + * @param callable $fixed + */ + public function __construct(callable $fixed) + { + $this->fixed = $fixed; + } + + public function __invoke(...$args) + { + return ($this->fixed)($this)(...$args); + } + + public function getDecorated() + { + return $this->fixed; + } +} \ No newline at end of file diff --git a/src/Utils/PartiallyAppliedFunction.php b/src/Utils/PartiallyAppliedFunction.php index 65e6681..3d75cfd 100644 --- a/src/Utils/PartiallyAppliedFunction.php +++ b/src/Utils/PartiallyAppliedFunction.php @@ -23,7 +23,7 @@ class PartiallyAppliedFunction implements Decorator { switch (true) { case $callable instanceof PartiallyAppliedFunction: - $this->callable = $callable->callable; + $this->callable = $callable->getDecorated(); $this->arguments = $callable->prepareArguments($args); break; @@ -63,4 +63,12 @@ class PartiallyAppliedFunction implements Decorator { return $this->callable; } + + public static function fix(callable $callable, ...$args) + { + $applied = new self($callable, ...$args); + array_unshift($applied->arguments, $applied); + + return $applied; + } } \ No newline at end of file diff --git a/src/Utils/index.php b/src/Utils/index.php index 9bad971..ae82d71 100644 --- a/src/Utils/index.php +++ b/src/Utils/index.php @@ -10,6 +10,7 @@ namespace Kadet\Functional; use Kadet\Functional\Utils\CurriedFunction; +use Kadet\Functional\Utils\FixedFunction; use Kadet\Functional\Utils\FunctionPipeline; use Kadet\Functional\Utils\PartiallyAppliedFunction; @@ -18,12 +19,21 @@ function partial(callable $callable, ...$args): PartiallyAppliedFunction return new PartiallyAppliedFunction($callable, ...$args); } +function apply(callable $callable, ...$args) +{ + $threshold = reflect($callable)->getNumberOfRequiredParameters(); + + return count($args) >= $threshold + ? $callable(...$args) + : partial($callable, ...$args); +} + function curry(callable $callable, int $threshold = null, ...$args): CurriedFunction { return new CurriedFunction($callable, $threshold, ...$args); } -function uncurry(CurriedFunction $curried) +function uncurry(CurriedFunction $curried): PartiallyAppliedFunction { return $curried->uncurry(); } @@ -32,3 +42,9 @@ function pipe(...$functions) { return new FunctionPipeline(...$functions); } + +function fix(callable $callable, ...$args) +{ + $f = new FixedFunction($callable); + return $args ? apply($f, ...$args) : $f; +} diff --git a/src/functions.php b/src/functions.php index 8ac6139..d55ac99 100644 --- a/src/functions.php +++ b/src/functions.php @@ -6,7 +6,10 @@ use Kadet\Functional\Predicate\AllOfPredicate; use Kadet\Functional\Predicate\AnyOfPredicate; use Kadet\Functional\Predicate\ClosurePredicate; use Kadet\Functional\Predicate\ConstantPredicate; +use function Kadet\Functional\Predicates\instance; +use Kadet\Functional\Reflection\ReflectionFixedFunction; use Kadet\Functional\Reflection\ReflectionPartiallyAppliedFunction; +use Kadet\Functional\Utils\FixedFunction; use Kadet\Functional\Utils\PartiallyAppliedFunction; require_once __DIR__.'/Utils/index.php'; @@ -73,6 +76,8 @@ function reflect(callable $callable): \ReflectionFunctionAbstract switch (true) { case $callable instanceof PartiallyAppliedFunction: return new ReflectionPartiallyAppliedFunction($callable); + case $callable instanceof FixedFunction: + return new ReflectionFixedFunction($callable); default: return new \ReflectionFunction($callable); } diff --git a/tests/PartialApplicationTest.php b/tests/PartialApplicationTest.php index f356478..6bed981 100644 --- a/tests/PartialApplicationTest.php +++ b/tests/PartialApplicationTest.php @@ -71,4 +71,17 @@ class PartialApplicationTest extends \PHPUnit\Framework\TestCase $applied->getArguments() ); } + + public function testYCombinator() + { + $applied = f\fix(function ($f) use (&$applied) { + $this->assertSame($f, $applied); + + return function ($x) { + $this->assertEquals(1, $x); + }; + }); + + $applied(1); + } } \ No newline at end of file