From 389e70658fb8227a44328fd90cd9b92dae769de6 Mon Sep 17 00:00:00 2001 From: Andrew Moyes Date: Fri, 20 Dec 2024 15:58:17 -0600 Subject: [PATCH 1/3] Update dev-dependencies - php-cs-fixer to 3.65 - phpunit to 10.0 - eris to 1.0 --- .gitignore | 2 +- .php_cs => .php-cs-fixer.dist.php | 12 +++--- composer.json | 8 ++-- phpstan.neon.dist | 4 ++ phpunit.xml.dist | 27 ++++++++------ .../Helpful/Tests/ApplicativeLawsTest.php | 19 ++++++---- .../Helpful/Tests/FunctorLawsTest.php | 13 ++++--- .../Helpful/Tests/MonadLawsTest.php | 13 ++++--- .../Helpful/Tests/StringMonoidLawsTest.php | 37 ++++++++++++++++--- .../Helpful/Tests/StringSetoidLawsTest.php | 2 +- src/FantasyLand/Useful/Identity.php | 2 +- src/FantasyLand/functions.php | 3 -- 12 files changed, 91 insertions(+), 51 deletions(-) rename .php_cs => .php-cs-fixer.dist.php (85%) create mode 100644 phpstan.neon.dist diff --git a/.gitignore b/.gitignore index c9dec9a..b4c96e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ /vendor/ -.php_cs.cache +.cache/ composer.lock diff --git a/.php_cs b/.php-cs-fixer.dist.php similarity index 85% rename from .php_cs rename to .php-cs-fixer.dist.php index 7187377..13ce596 100644 --- a/.php_cs +++ b/.php-cs-fixer.dist.php @@ -1,9 +1,10 @@ exclude('vendor') ->in(__DIR__); -return PhpCsFixer\Config::create() +return (new PhpCsFixer\Config()) ->setUsingCache(true) + ->setCacheFile('.cache/php-cs-fixer/php-cs-fixer.cache') ->setRules([ '@PSR2' => true, 'encoding' => true, @@ -14,7 +15,7 @@ 'indentation_type' => true, 'blank_line_after_namespace' => true, 'line_ending' => true, - 'lowercase_constants' => true, + 'constant_case' => ['case' => 'lower'], 'lowercase_keywords' => true, 'no_closing_tag' => true, 'single_line_after_imports' => true, @@ -23,7 +24,7 @@ 'whitespace_after_comma_in_array' => true, 'blank_line_after_opening_tag' => true, 'no_empty_statement' => true, - 'no_extra_consecutive_blank_lines' => true, + 'no_extra_blank_lines' => true, 'function_typehint_space' => true, 'no_leading_namespace_whitespace' => true, 'no_blank_lines_after_class_opening' => true, @@ -31,8 +32,7 @@ 'phpdoc_scalar' => true, 'phpdoc_types' => true, 'no_leading_import_slash' => true, - 'no_extra_consecutive_blank_lines' => ['use'], - 'blank_line_before_return' => true, + 'blank_line_before_statement' => ['statements' => ['return']], 'self_accessor' => false, 'no_short_bool_cast' => true, 'no_trailing_comma_in_singleline_array' => true, diff --git a/composer.json b/composer.json index 4ca93a6..0209c62 100644 --- a/composer.json +++ b/composer.json @@ -13,9 +13,11 @@ "php": ">=7.1" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^2", - "phpunit/phpunit": "^5", - "giorgiosironi/eris": "^0.9" + "friendsofphp/php-cs-fixer": "^3.65", + "phpunit/phpunit": "^10.0", + "giorgiosironi/eris": "^1.0", + "phpstan/phpstan": "^2.0", + "vimeo/psalm": "^5.26" }, "prefer-stable": true, "autoload": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..9228327 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,4 @@ +parameters: + level: 10 + paths: + - src diff --git a/phpunit.xml.dist b/phpunit.xml.dist index f1ee651..3103287 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,14 +1,17 @@ - - - - ./src/FantasyLand/Helpful/Tests - - - - - ./src/ - - + + + + ./src/FantasyLand/Helpful/Tests + + + + + ./src/ + + diff --git a/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php b/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php index 82bc2cc..315457c 100644 --- a/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php @@ -8,7 +8,7 @@ use FunctionalPHP\FantasyLand\Helpful\ApplicativeLaws; use FunctionalPHP\FantasyLand\Useful\Identity; -class ApplicativeLawsTest extends \PHPUnit_Framework_TestCase +class ApplicativeLawsTest extends \PHPUnit\Framework\TestCase { /** * @dataProvider provideApplicativeTestData @@ -31,23 +31,26 @@ public function test_it_should_obey_applicative_laws( ); } - public function provideApplicativeTestData() + /** + * @return array{default: array} + */ + public static function provideApplicativeTestData() { return [ 'default' => [ - '$u' => Identity::of(function () { + 'x' => 33, + 'u' => Identity::of(function () { return 1; }), - '$v' => Identity::of(function () { + 'v' => Identity::of(function () { return 5; }), - '$w' => Identity::of(function () { + 'w' => Identity::of(function () { return 7; }), - '$f' => function ($x) { + 'f' => function (int $x) { return $x + 400; - }, - '$x' => 33 + } ], ]; } diff --git a/src/FantasyLand/Helpful/Tests/FunctorLawsTest.php b/src/FantasyLand/Helpful/Tests/FunctorLawsTest.php index 5264526..39ae918 100644 --- a/src/FantasyLand/Helpful/Tests/FunctorLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/FunctorLawsTest.php @@ -8,7 +8,7 @@ use FunctionalPHP\FantasyLand\Helpful\FunctorLaws; use FunctionalPHP\FantasyLand\Useful\Identity; -class FunctorLawsTest extends \PHPUnit_Framework_TestCase +class FunctorLawsTest extends \PHPUnit\Framework\TestCase { /** * @dataProvider provideFunctorTestData @@ -26,17 +26,20 @@ public function test_it_should_obey_functor_laws( ); } - public function provideFunctorTestData() + /** + * @return array{Identity: array} + */ + public static function provideFunctorTestData(): array { return [ 'Identity' => [ - '$f' => function ($x) { + 'f' => function (int $x) { return $x + 1; }, - '$g' => function ($x) { + 'g' => function (int $x) { return $x + 5; }, - '$x' => Identity::of(123), + 'x' => Identity::of(123), ], ]; } diff --git a/src/FantasyLand/Helpful/Tests/MonadLawsTest.php b/src/FantasyLand/Helpful/Tests/MonadLawsTest.php index 2137284..609de13 100644 --- a/src/FantasyLand/Helpful/Tests/MonadLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/MonadLawsTest.php @@ -7,7 +7,7 @@ use FunctionalPHP\FantasyLand\Helpful\MonadLaws; use FunctionalPHP\FantasyLand\Useful\Identity; -class MonadLawsTest extends \PHPUnit_Framework_TestCase +class MonadLawsTest extends \PHPUnit\Framework\TestCase { /** * @dataProvider provideData @@ -23,7 +23,10 @@ public function test_if_identity_monad_obeys_the_laws($f, $g, $x) ); } - public function provideData() + /** + * @return array{Identity: array} + */ + public static function provideData(): array { $addOne = function ($x) { return Identity::of($x + 1); @@ -34,9 +37,9 @@ public function provideData() return [ 'Identity' => [ - '$f' => $addOne, - '$g' => $addTwo, - '$x' => 10, + 'x' => 10, + 'f' => $addOne, + 'g' => $addTwo, ], ]; } diff --git a/src/FantasyLand/Helpful/Tests/StringMonoidLawsTest.php b/src/FantasyLand/Helpful/Tests/StringMonoidLawsTest.php index 806fea0..22a9134 100644 --- a/src/FantasyLand/Helpful/Tests/StringMonoidLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/StringMonoidLawsTest.php @@ -9,6 +9,8 @@ use FunctionalPHP\FantasyLand\Helpful\MonoidLaws; use FunctionalPHP\FantasyLand\Monoid; use FunctionalPHP\FantasyLand\Semigroup; +use function FunctionalPHP\FantasyLand\concat; +use function FunctionalPHP\FantasyLand\emptyy; class StringMonoid implements Monoid { @@ -39,6 +41,14 @@ public function concat(Semigroup $value): Semigroup } } +/** + * This class is not a monoid because it does not obey the monoid laws, + * despite implementing the Monoid interface. In particular, it does not obay + * the right identity and left identity laws, due to the way the `mempty` method + * is implemented. + * + * @implements Monoid + */ class NotAStringMonoid implements Monoid { /** @@ -68,7 +78,7 @@ public function concat(Semigroup $value): Semigroup } } -class StringMonoidLawsTest extends \PHPUnit_Framework_TestCase +class StringMonoidLawsTest extends \PHPUnit\Framework\TestCase { use TestTrait; @@ -98,11 +108,26 @@ public function test_it_should_fail_monoid_laws() Generator\string(), Generator\names() )->then(function (string $a, string $b, string $c) { - MonoidLaws::test( - [$this, 'assertEquals'], - new NotAStringMonoid($a), - new NotAStringMonoid($b), - new NotAStringMonoid($c) + $x = new NotAStringMonoid($a); + $y = new NotAStringMonoid($b); + $z = new NotAStringMonoid($c); + + $this->assertNotEquals( + concat($x, emptyy($x)), + $x, + 'Right identity' + ); + + $this->assertNotEquals( + concat(emptyy($x), $x), + $x, + 'Left identity' + ); + + $this->assertEquals( + concat($x, concat($y, $z)), + concat(concat($x, $y), $z), + 'Associativity' ); }); } diff --git a/src/FantasyLand/Helpful/Tests/StringSetoidLawsTest.php b/src/FantasyLand/Helpful/Tests/StringSetoidLawsTest.php index ff0ebc6..c936f5c 100644 --- a/src/FantasyLand/Helpful/Tests/StringSetoidLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/StringSetoidLawsTest.php @@ -32,7 +32,7 @@ public function equals($other): bool } } -class StringSetoidLawsTest extends \PHPUnit_Framework_TestCase +class StringSetoidLawsTest extends \PHPUnit\Framework\TestCase { use TestTrait; diff --git a/src/FantasyLand/Useful/Identity.php b/src/FantasyLand/Useful/Identity.php index 042cfab..96a9ed1 100644 --- a/src/FantasyLand/Useful/Identity.php +++ b/src/FantasyLand/Useful/Identity.php @@ -12,7 +12,7 @@ */ class Identity implements FantasyLand\Monad { - const of = 'FunctionalPHP\FantasyLand\Useful\Identity::of'; + public const of = 'FunctionalPHP\FantasyLand\Useful\Identity::of'; /** * @var mixed diff --git a/src/FantasyLand/functions.php b/src/FantasyLand/functions.php index 41c2f59..0dec0eb 100644 --- a/src/FantasyLand/functions.php +++ b/src/FantasyLand/functions.php @@ -63,7 +63,6 @@ function emptyy(Monoid $a): Monoid return $a::mempty(); } - /** * @var callable */ @@ -143,7 +142,6 @@ function compose(callable $f, callable $g): callable }; } - /** * @var callable */ @@ -171,7 +169,6 @@ function applicator($x, ?callable $f = null) })(...func_get_args()); } - /** * Curry function * From 1cd84e18d763b7f89a91202c045715cf58fcc8b6 Mon Sep 17 00:00:00 2001 From: Andrew Moyes Date: Fri, 20 Dec 2024 18:02:56 -0600 Subject: [PATCH 2/3] Fix all PHPStan errors, including docblock issues. fixes #22 --- composer.json | 3 +- src/FantasyLand/Applicative.php | 4 +- src/FantasyLand/Apply.php | 2 +- src/FantasyLand/Chain.php | 5 +- src/FantasyLand/Foldable.php | 5 +- src/FantasyLand/Functor.php | 3 +- src/FantasyLand/Helpful/ApplicativeLaws.php | 37 ++++++++---- src/FantasyLand/Helpful/FunctorLaws.php | 31 +++++++--- src/FantasyLand/Helpful/MonadLaws.php | 49 +++++++++++---- src/FantasyLand/Helpful/MonoidLaws.php | 12 ++-- src/FantasyLand/Helpful/SetoidLaws.php | 14 +++-- .../Helpful/Tests/ApplicativeLawsTest.php | 34 +++++++++-- .../Helpful/Tests/FunctorLawsTest.php | 10 +++- .../Helpful/Tests/MonadLawsTest.php | 35 +++++++++-- .../Helpful/Tests/StringMonoidLawsTest.php | 32 ++++++---- .../Helpful/Tests/StringSetoidLawsTest.php | 13 ++-- src/FantasyLand/Monad.php | 4 +- src/FantasyLand/Monoid.php | 2 +- src/FantasyLand/Pointed.php | 6 +- src/FantasyLand/Setoid.php | 2 +- src/FantasyLand/Traversable.php | 5 +- src/FantasyLand/Useful/Identity.php | 60 +++++++++++++++++-- src/FantasyLand/functions.php | 54 ++++++++--------- 23 files changed, 296 insertions(+), 126 deletions(-) diff --git a/composer.json b/composer.json index 0209c62..f584f66 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "scripts": { "test": "phpunit --no-coverage", "fix-code": "php-cs-fixer fix --allow-risky=yes", - "check-code": "php-cs-fixer fix --verbose --diff --dry-run --allow-risky=yes" + "check-code": "php-cs-fixer fix --verbose --diff --dry-run --allow-risky=yes", + "static-analysis": "phpstan analyse" } } diff --git a/src/FantasyLand/Applicative.php b/src/FantasyLand/Applicative.php index fdd2c1e..61ca86b 100644 --- a/src/FantasyLand/Applicative.php +++ b/src/FantasyLand/Applicative.php @@ -6,8 +6,8 @@ /** * @template a - * @template-extends Apply - * @template-extends Pointed + * @extends Apply + * @extends Pointed */ interface Applicative extends Apply, diff --git a/src/FantasyLand/Apply.php b/src/FantasyLand/Apply.php index 9469935..8e682b2 100644 --- a/src/FantasyLand/Apply.php +++ b/src/FantasyLand/Apply.php @@ -6,7 +6,7 @@ /** * @template a - * @template-extends Functor + * @extends Functor */ interface Apply extends Functor { diff --git a/src/FantasyLand/Chain.php b/src/FantasyLand/Chain.php index a3d3527..fa9c65d 100644 --- a/src/FantasyLand/Chain.php +++ b/src/FantasyLand/Chain.php @@ -6,7 +6,7 @@ /** * @template a - * @template-extends Apply + * @extends Apply */ interface Chain extends Apply { @@ -14,9 +14,8 @@ interface Chain extends Apply * bind :: Monad m => (a -> m b) -> m b * * @template b - * @template f of callable(a): Chain * - * @param f $function + * @param callable(a): Chain $function * * @return Chain */ diff --git a/src/FantasyLand/Foldable.php b/src/FantasyLand/Foldable.php index 323a457..891fad7 100644 --- a/src/FantasyLand/Foldable.php +++ b/src/FantasyLand/Foldable.php @@ -13,10 +13,9 @@ interface Foldable * reduce :: (b -> a -> b) -> b -> b * * @template b - * @template f of callable(b, a): b * - * @param f $function Binary function ($accumulator, $value) - * @param b $accumulator Value to witch reduce + * @param callable(b, a): b $function Binary function ($accumulator, $value) + * @param b $accumulator Value to witch reduce * * @return b Same type as $accumulator */ diff --git a/src/FantasyLand/Functor.php b/src/FantasyLand/Functor.php index a8efd6b..84f7b9a 100644 --- a/src/FantasyLand/Functor.php +++ b/src/FantasyLand/Functor.php @@ -13,9 +13,8 @@ interface Functor * map :: Functor f => (a -> b) -> f b * * @template b - * @template f of callable(a): b * - * @param f $function + * @param callable(a): b $function * * @return Functor */ diff --git a/src/FantasyLand/Helpful/ApplicativeLaws.php b/src/FantasyLand/Helpful/ApplicativeLaws.php index 23a3890..5a633e4 100644 --- a/src/FantasyLand/Helpful/ApplicativeLaws.php +++ b/src/FantasyLand/Helpful/ApplicativeLaws.php @@ -15,23 +15,28 @@ class ApplicativeLaws /** * Generic test to verify if a type obey the applicative laws. * - * @param callable $assertEqual Asserting function (Applicative $a1, Applicative $a2, $message) - * @param callable $pure Applicative "constructor" - * @param Applicative $u Applicative f => f (a -> b) - * @param Applicative $v Applicative f => f (a -> b) - * @param Applicative $w Applicative f => f (a -> b) - * @param callable $f (a -> b) - * @param mixed $x Value to put into a applicative + * @template a + * @template b + * @template c + * @template d + * + * @param callable $assertEqual Asserting function (Applicative $a1, Applicative $a2, $message) + * @param a $x Value to put into a applicative + * @param callable(mixed): Applicative $pure Applicative "constructor" + * @param Applicative $u Applicative f => f (a -> b) + * @param Applicative $v Applicative f => f (a -> b) + * @param Applicative $w Applicative f => f (a -> b) + * @param callable(a): b $f (a -> b) */ public static function test( callable $assertEqual, + $x, callable $pure, Applicative $u, Applicative $v, Applicative $w, - callable $f, - $x - ) { + callable $f + ): void { // identity: pure id <*> v = v $assertEqual( $pure(identity)->ap($v), @@ -47,9 +52,19 @@ public static function test( ); // interchange: u <*> pure x = pure ($ x) <*> u + /** + * @var callable(callable(a): b): b $ap + * + * PHPStan has no idea how currying works and it's not able to infer + * the types of the singly-applied applicator() calls. As such, we'll + * suppress the errors here. + * + * @phpstan-ignore argument.templateType,varTag.differentVariable + */ + $applicatorX = applicator($x); $assertEqual( $u->ap($pure($x)), - $pure(applicator($x))->ap($u), + $pure($applicatorX)->ap($u), 'interchange' ); diff --git a/src/FantasyLand/Helpful/FunctorLaws.php b/src/FantasyLand/Helpful/FunctorLaws.php index ea6bcb8..2c0dd0c 100644 --- a/src/FantasyLand/Helpful/FunctorLaws.php +++ b/src/FantasyLand/Helpful/FunctorLaws.php @@ -5,7 +5,6 @@ namespace FunctionalPHP\FantasyLand\Helpful; use FunctionalPHP\FantasyLand\Functor; -use const FunctionalPHP\FantasyLand\identity; use function FunctionalPHP\FantasyLand\compose; use function FunctionalPHP\FantasyLand\map; @@ -14,20 +13,34 @@ class FunctorLaws /** * Generic test to verify if a type obey the functor laws. * - * @param callable $assertEqual Asserting function (Functor $f1, Functor $f2, $message) - * @param callable $f (a -> b) - * @param callable $g (a -> b) - * @param Functor $x f a + * @template a + * @template b + * @template c + * + * @param callable $assertEqual Asserting function (Functor $f1, Functor $f2, $message) + * @param callable(b): c $f (a -> b) + * @param callable(a): b $g (a -> b) + * @param Functor $x f a */ public static function test( callable $assertEqual, callable $f, callable $g, Functor $x - ) { + ): void { + $identity = + /** + * @template a + * @param a $x + * @return a + */ + static function ($x) { + return $x; + }; + // identity: fmap id == id $assertEqual( - map(identity, $x), + map($identity, $x), $x, 'identity' ); @@ -35,6 +48,10 @@ public static function test( // composition: fmap (f . g) == fmap f . fmap g $assertEqual( map(compose($f, $g), $x), + // PHPStan has no idea how currying works and it's not able to infer + // the types of the singly-applied map() calls. As such, we'll + // suppress the error here. + // @phpstan-ignore argument.type,argument.type compose(map($f), map($g))($x), 'composition' ); diff --git a/src/FantasyLand/Helpful/MonadLaws.php b/src/FantasyLand/Helpful/MonadLaws.php index 0fa4cce..86585b3 100644 --- a/src/FantasyLand/Helpful/MonadLaws.php +++ b/src/FantasyLand/Helpful/MonadLaws.php @@ -4,6 +4,8 @@ namespace FunctionalPHP\FantasyLand\Helpful; +use FunctionalPHP\FantasyLand\Monad; + use function FunctionalPHP\FantasyLand\bind; class MonadLaws @@ -11,34 +13,55 @@ class MonadLaws /** * Generic test to verify if a type obey the monad laws. * - * @param callable $assertEqual Asserting function (Monad $m1, Monad $m2, $message) - * @param callable $return Monad "constructor" - * @param callable $f Monadic function - * @param callable $g Monadic function - * @param mixed $x Value to put into a monad + * @template a + * @template b + * @template c + * + * @param callable $assertEqual Asserting function (Monad $m1, Monad $m2, $message) + * @param a $x Value to put into a monad + * @param callable(a): Monad $return Monad "constructor" + * @param callable(a): Monad $f Monadic function + * @param callable(b): Monad $g Monadic function */ public static function test( callable $assertEqual, + $x, callable $return, callable $f, - callable $g, - $x - ) { + callable $g + ): void { // Make reading bellow tests easier $m = $return($x); // left identity: (return x) >>= f ≡ f x - $assertEqual(bind($f, $return($x)), $f($x), 'left identity'); + $assertEqual( + bind($f, $return($x)), + $f($x), + 'left identity' + ); // right identity: m >>= return ≡ m $assertEqual(bind($return, $m), $m, 'right identity'); // associativity: (m >>= f) >>= g ≡ m >>= ( \x -> (f x >>= g) ) - $assertEqual( - bind($g, bind($f, $m)), - bind(function ($x) use ($f, $g) { + + /** @var Monad $boundF */ + $boundF = bind($f, $m); + /** @var Monad $boundG */ + $boundG = bind($g, $boundF); + $boundFoG = + /** + * @param a $x + * @return Monad + */ + function ($x) use ($f, $g) { + /** @var Monad */ return bind($g, $f($x)); - }, $m), + }; + + $assertEqual( + $boundG, + bind($boundFoG, $m), 'associativity' ); } diff --git a/src/FantasyLand/Helpful/MonoidLaws.php b/src/FantasyLand/Helpful/MonoidLaws.php index a6f140d..5ad09ea 100644 --- a/src/FantasyLand/Helpful/MonoidLaws.php +++ b/src/FantasyLand/Helpful/MonoidLaws.php @@ -13,17 +13,19 @@ class MonoidLaws /** * Generic test to verify if a type obey the monodic laws. * - * @param callable $assertEqual Asserting function (Monoid $m1, Monoid $m2, $message) - * @param Monoid $x - * @param Monoid $y - * @param Monoid $z + * @template a + * + * @param callable $assertEqual Asserting function (Monoid $m1, Monoid $m2, $message) + * @param Monoid $x + * @param Monoid $y + * @param Monoid $z */ public static function test( callable $assertEqual, Monoid $x, Monoid $y, Monoid $z - ) { + ): void { $assertEqual( concat($x, emptyy($x)), $x, diff --git a/src/FantasyLand/Helpful/SetoidLaws.php b/src/FantasyLand/Helpful/SetoidLaws.php index 29d1c73..bbe1dd4 100644 --- a/src/FantasyLand/Helpful/SetoidLaws.php +++ b/src/FantasyLand/Helpful/SetoidLaws.php @@ -10,17 +10,21 @@ class SetoidLaws { /** - * @param callable $assertEqual - * @param Setoid $a - * @param Setoid $b - * @param Setoid $c + * @template a + * @template b + * @template c + * + * @param callable $assertEqual + * @param Setoid $a + * @param Setoid $b + * @param Setoid $c */ public static function test( callable $assertEqual, Setoid $a, Setoid $b, Setoid $c - ) { + ): void { $assertEqual( equal($a, $a), true, diff --git a/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php b/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php index 315457c..bdbd54a 100644 --- a/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php @@ -12,22 +12,44 @@ class ApplicativeLawsTest extends \PHPUnit\Framework\TestCase { /** * @dataProvider provideApplicativeTestData + * + * @template a + * @template b + * @template c + * @template d + * + * @param a $x + * @param Applicative $u + * @param Applicative $v + * @param Applicative $w + * @param callable(a): b $f */ public function test_it_should_obey_applicative_laws( + $x, Applicative $u, Applicative $v, Applicative $w, - callable $f, - $x - ) { + callable $f + ): void { + // This is a workaround to allow static analysis to infer the types of + // the `pure` function. + $pure = + /** + * @param a $x + * @return Identity + */ + function ($x) { + return Identity::of($x); + }; + ApplicativeLaws::test( [$this, 'assertEquals'], - Identity::of, + $x, + $pure, $u, $v, $w, - $f, - $x + $f ); } diff --git a/src/FantasyLand/Helpful/Tests/FunctorLawsTest.php b/src/FantasyLand/Helpful/Tests/FunctorLawsTest.php index 39ae918..be17649 100644 --- a/src/FantasyLand/Helpful/Tests/FunctorLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/FunctorLawsTest.php @@ -12,12 +12,20 @@ class FunctorLawsTest extends \PHPUnit\Framework\TestCase { /** * @dataProvider provideFunctorTestData + * + * @template a + * @template b + * @template c + * + * @param callable(b): c $f + * @param callable(a): b $g + * @param Functor $x */ public function test_it_should_obey_functor_laws( callable $f, callable $g, Functor $x - ) { + ): void { FunctorLaws::test( [$this, 'assertEquals'], $f, diff --git a/src/FantasyLand/Helpful/Tests/MonadLawsTest.php b/src/FantasyLand/Helpful/Tests/MonadLawsTest.php index 609de13..db18b00 100644 --- a/src/FantasyLand/Helpful/Tests/MonadLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/MonadLawsTest.php @@ -11,15 +11,38 @@ class MonadLawsTest extends \PHPUnit\Framework\TestCase { /** * @dataProvider provideData + * + * @template a + * @template b + * @template c + * + * @param a $x + * @param callable(a): Identity $f + * @param callable(b): Identity $g */ - public function test_if_identity_monad_obeys_the_laws($f, $g, $x) + public function test_if_identity_monad_obeys_the_laws($x, $f, $g): void { + // This is a workaround to allow static analysis to infer the types of + // the `pure` function. + $pure = + /** + * @param a $x + * @return Identity + */ + function ($x) { + return Identity::of($x); + }; + MonadLaws::test( [$this, 'assertEquals'], - Identity::of, + $x, + $pure, + // PHPStan seems to have lost track of `a` here, and is unable to + // infer that `$f` should be callable(a): Identity. It seems to + // have inferred that `$f` is callable(mixed): Identity. + // @phpstan-ignore argument.type $f, - $g, - $x + $g ); } @@ -28,10 +51,10 @@ public function test_if_identity_monad_obeys_the_laws($f, $g, $x) */ public static function provideData(): array { - $addOne = function ($x) { + $addOne = function (int $x) { return Identity::of($x + 1); }; - $addTwo = function ($x) { + $addTwo = function (int $x) { return Identity::of($x + 2); }; diff --git a/src/FantasyLand/Helpful/Tests/StringMonoidLawsTest.php b/src/FantasyLand/Helpful/Tests/StringMonoidLawsTest.php index 22a9134..e1c9d57 100644 --- a/src/FantasyLand/Helpful/Tests/StringMonoidLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/StringMonoidLawsTest.php @@ -4,7 +4,7 @@ namespace FunctionalPHP\FantasyLand\Helpful\Tests; -use Eris\Generator; +use Eris\Generators; use Eris\TestTrait; use FunctionalPHP\FantasyLand\Helpful\MonoidLaws; use FunctionalPHP\FantasyLand\Monoid; @@ -12,6 +12,11 @@ use function FunctionalPHP\FantasyLand\concat; use function FunctionalPHP\FantasyLand\emptyy; +/** + * This class is a monoid because it obeys the monoid laws. + * + * @implements Monoid + */ class StringMonoid implements Monoid { /** @@ -26,6 +31,7 @@ public function __construct(string $value) /** * @inheritdoc + * @return StringMonoid */ public static function mempty() { @@ -34,6 +40,8 @@ public static function mempty() /** * @inheritdoc + * @param StringMonoid $value + * @return StringMonoid */ public function concat(Semigroup $value): Semigroup { @@ -63,6 +71,7 @@ public function __construct(string $value) /** * @inheritdoc + * @return NotAStringMonoid */ public static function mempty() { @@ -71,6 +80,8 @@ public static function mempty() /** * @inheritdoc + * @param NotAStringMonoid $value + * @return NotAStringMonoid */ public function concat(Semigroup $value): Semigroup { @@ -82,12 +93,12 @@ class StringMonoidLawsTest extends \PHPUnit\Framework\TestCase { use TestTrait; - public function test_it_should_obay_monoid_laws() + public function test_it_should_obay_monoid_laws(): void { $this->forAll( - Generator\char(), - Generator\string(), - Generator\names() + Generators::char(), + Generators::string(), + Generators::names() )->then(function (string $a, string $b, string $c) { MonoidLaws::test( [$this, 'assertEquals'], @@ -98,15 +109,12 @@ public function test_it_should_obay_monoid_laws() }); } - /** - * @expectedException \DomainException - */ - public function test_it_should_fail_monoid_laws() + public function test_it_should_fail_monoid_laws(): void { $this->forAll( - Generator\char(), - Generator\string(), - Generator\names() + Generators::char(), + Generators::string(), + Generators::names() )->then(function (string $a, string $b, string $c) { $x = new NotAStringMonoid($a); $y = new NotAStringMonoid($b); diff --git a/src/FantasyLand/Helpful/Tests/StringSetoidLawsTest.php b/src/FantasyLand/Helpful/Tests/StringSetoidLawsTest.php index c936f5c..cb3ffc4 100644 --- a/src/FantasyLand/Helpful/Tests/StringSetoidLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/StringSetoidLawsTest.php @@ -4,11 +4,14 @@ namespace FunctionalPHP\FantasyLand\Helpful\Tests; -use Eris\Generator; +use Eris\Generators; use Eris\TestTrait; use FunctionalPHP\FantasyLand\Helpful\SetoidLaws; use FunctionalPHP\FantasyLand\Setoid; +/** + * @implements Setoid + */ class StringSetoid implements Setoid { /** @@ -36,12 +39,12 @@ class StringSetoidLawsTest extends \PHPUnit\Framework\TestCase { use TestTrait; - public function test_it_should_obay_setoid_laws() + public function test_it_should_obay_setoid_laws(): void { $this->forAll( - Generator\char(), - Generator\string(), - Generator\names() + Generators::char(), + Generators::string(), + Generators::names() )->then(function (string $a, string $b, string $c) { SetoidLaws::test( [$this, 'assertEquals'], diff --git a/src/FantasyLand/Monad.php b/src/FantasyLand/Monad.php index c245c35..b352345 100644 --- a/src/FantasyLand/Monad.php +++ b/src/FantasyLand/Monad.php @@ -6,8 +6,8 @@ /** * @template a - * @template-extends Applicative - * @template-extends Chain + * @extends Applicative + * @extends Chain */ interface Monad extends Applicative, diff --git a/src/FantasyLand/Monoid.php b/src/FantasyLand/Monoid.php index 43f76de..d281116 100644 --- a/src/FantasyLand/Monoid.php +++ b/src/FantasyLand/Monoid.php @@ -6,7 +6,7 @@ /** * @template a - * @template-extends Semigroup + * @extends Semigroup */ interface Monoid extends Semigroup { diff --git a/src/FantasyLand/Pointed.php b/src/FantasyLand/Pointed.php index 97b1032..a9f80e2 100644 --- a/src/FantasyLand/Pointed.php +++ b/src/FantasyLand/Pointed.php @@ -12,9 +12,11 @@ interface Pointed /** * Put $value in default minimal context. * - * @param a $value + * @template A * - * @return Pointed + * @param A $value + * + * @return Pointed */ public static function of($value); } diff --git a/src/FantasyLand/Setoid.php b/src/FantasyLand/Setoid.php index 4a37c72..7d87cb2 100644 --- a/src/FantasyLand/Setoid.php +++ b/src/FantasyLand/Setoid.php @@ -12,7 +12,7 @@ interface Setoid /** * @template b * - * @param Setoid|mixed $other + * @param Setoid $other * * @return bool */ diff --git a/src/FantasyLand/Traversable.php b/src/FantasyLand/Traversable.php index 76cd9c3..4e977eb 100644 --- a/src/FantasyLand/Traversable.php +++ b/src/FantasyLand/Traversable.php @@ -6,7 +6,7 @@ /** * @template a - * @template-extends Functor + * @extends Functor */ interface Traversable extends Functor { @@ -16,9 +16,8 @@ interface Traversable extends Functor * Where the `a` is value inside of container. * * @template b - * @template fn of callable(a): Applicative * - * @param fn $fn (a -> f b) + * @param callable(a): Applicative $fn (a -> f b) * * @return Applicative> f (t b) */ diff --git a/src/FantasyLand/Useful/Identity.php b/src/FantasyLand/Useful/Identity.php index 96a9ed1..c66c85b 100644 --- a/src/FantasyLand/Useful/Identity.php +++ b/src/FantasyLand/Useful/Identity.php @@ -8,25 +8,31 @@ /** * @template a - * @template-extends FantasyLand\Monad + * @implements FantasyLand\Monad */ class Identity implements FantasyLand\Monad { public const of = 'FunctionalPHP\FantasyLand\Useful\Identity::of'; /** - * @var mixed + * @var a */ private $value; /** * @inheritdoc + * @template A + * @param A $value + * @return Identity */ public static function of($value) { - return new self($value); + return new Identity($value); } + /** + * @param a $value + */ private function __construct($value) { $this->value = $value; @@ -34,18 +40,60 @@ private function __construct($value) /** * @inheritdoc + * @template b + * @param callable(a): b $transformation + * @return Identity */ - public function map(callable $transformation): FantasyLand\Functor + public function map(callable $transformation): Identity { - return static::of($this->bind($transformation)); + /** + * This is a workaround so that static analysis tools can understand + * the type signature of the callables. + * + * @param a $x + * @return Identity + */ + $fn = static function ($x) use ($transformation) { + return Identity::of($transformation($x)); + }; + + /** @var Identity */ + return $this->bind($fn); } /** * @inheritdoc + * + * TODO: Not sure how to write the type signature for this method; it seems + * to rely on the current `a` type being a subtype of `callable(b): c` + * which cannot be guaranteed by the type system as this may have been + * instantiated with any type. Calling `->ap(...)` on an instance of + * `Identity` with a non-callable value will result in a runtime error + * that cannot be caught by the type system. Additionally, if the + * `Identity` instance has a callable value, we still cannot guarentee + * that the types line up correctly, so really anything could happen + * here. + * + * @template b + * @param Identity $applicative + * @return Identity|never */ public function ap(FantasyLand\Apply $applicative): FantasyLand\Apply { - return $applicative->map($this->value); + $value = $this->value; + + if (!is_callable($value)) { + throw new \TypeError('Cannot call `ap` on an Identity instance with a non-callable value'); + } + + /** + * Being optimistic and hoping that _if_ $value is callable then the + * types should line up correctly (assuming the user of the function + * hasn't provided a callable of a different type). + * + * @var callable(b): mixed $value + */ + return $applicative->map($value); } /** diff --git a/src/FantasyLand/functions.php b/src/FantasyLand/functions.php index 0dec0eb..9f286a6 100644 --- a/src/FantasyLand/functions.php +++ b/src/FantasyLand/functions.php @@ -73,19 +73,18 @@ function emptyy(Monoid $a): Monoid * * @template a * @template b - * @template f of callable(a): b - * @template next of callable(Functor): Functor * - * @param f $transformation + * @param callable(a): b $transformation * @param Functor|null $value * - * @return Functor|next If a functor was provided directly to map, returns - * the result of applying the transformation to the - * value. Otherwise, returns a curried function that - * expects a functor. + * @return Functor|(callable(Functor): Functor) + * If a functor was provided directly to map, returns the result of + * applying the transformation to the value. Otherwise, returns a curried + * function that expects a functor. */ function map(callable $transformation, ?Functor $value = null) { + /** @var Functor|(callable(Functor): Functor) */ return curryN(2, function (callable $transformation, Functor $value) { return $value->map($transformation); })(...func_get_args()); @@ -101,19 +100,22 @@ function map(callable $transformation, ?Functor $value = null) * * @template a * @template b - * @template f of callable(a): Monad - * @template next of callable(Monad): Monad * - * @param f $function - * @param Monad|null $value + * @param callable(a): Monad $function + * @param Monad|null $value * - * @return Monad|next If a monad was provided directly to bind, returns the - * result. Otherwise, returns a curried function that - * expects a monad. + * @return Monad|(callable(Monad): Monad) + * If a monad was provided directly to bind, returns the result. Otherwise, + * returns a curried function that expects a monad. */ function bind(callable $function, ?Monad $value = null) { + /** @var Monad|(callable(Monad): Monad) */ return curryN(2, function (callable $function, Monad $value) { + /** + * @var callable(a): Monad $function + * @var Monad $value + */ return $value->bind($function); })(...func_get_args()); } @@ -127,13 +129,10 @@ function bind(callable $function, ?Monad $value = null) * @template a * @template b * @template c - * @template f of callable(b): c - * @template g of callable(a): b - * @template composed of callable(a): c * - * @param f $f - * @param g $g - * @return composed + * @param callable(b): c $f + * @param callable(a): b $g + * @return callable(a): c */ function compose(callable $f, callable $g): callable { @@ -152,15 +151,14 @@ function compose(callable $f, callable $g): callable * * @template a * @template b - * @template f of callable(a): b - * @template next of callable(f): b * - * @param a $x - * @param f|null $f + * @param a $x + * @param callable(a): b|null $f * - * @return b|next If a function was provided directly to applicator, returns the - * result of applying the function to the value. Otherwise, - * returns a curried function that expects said function. + * @return b|(callable(callable(a): b): b) + * If a function was provided directly to applicator, returns the result of + * applying the function to the value. Otherwise, returns a curried + * function that expects said function. */ function applicator($x, ?callable $f = null) { @@ -174,7 +172,7 @@ function applicator($x, ?callable $f = null) * * @param int $numberOfArguments * @param callable $function - * @param array $args + * @param mixed[] $args * * @return callable */ From d1a104e3f380edd7c3cc8a3bdfc2b724aff2f66a Mon Sep 17 00:00:00 2001 From: Andrew Moyes Date: Fri, 20 Dec 2024 19:26:28 -0600 Subject: [PATCH 3/3] Add Psalm to validate Psalm also checks nicely --- composer.json | 3 +- phpstan-baseline.neon | 31 +++++++++++ phpstan.neon.dist | 4 ++ psalm.baseline.xml | 9 ++++ psalm.xml | 47 ++++++++++++++++ src/FantasyLand/Functor.php | 2 +- src/FantasyLand/Helpful/ApplicativeLaws.php | 10 +--- src/FantasyLand/Helpful/FunctorLaws.php | 5 -- .../Helpful/Tests/ApplicativeLawsTest.php | 2 +- .../Helpful/Tests/MonadLawsTest.php | 10 ++-- src/FantasyLand/Useful/Identity.php | 23 ++++---- src/FantasyLand/functions.php | 54 +++++++++++-------- 12 files changed, 144 insertions(+), 56 deletions(-) create mode 100644 phpstan-baseline.neon create mode 100644 psalm.baseline.xml create mode 100644 psalm.xml diff --git a/composer.json b/composer.json index f584f66..7e8cb5e 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "test": "phpunit --no-coverage", "fix-code": "php-cs-fixer fix --allow-risky=yes", "check-code": "php-cs-fixer fix --verbose --diff --dry-run --allow-risky=yes", - "static-analysis": "phpstan analyse" + "phpstan": "phpstan analyse", + "psalm": "psalm" } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..34a69d8 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,31 @@ +parameters: + ignoreErrors: + - + message: '#^Unable to resolve the template type b in call to function FunctionalPHP\\FantasyLand\\applicator$#' + identifier: argument.templateType + count: 1 + path: src/FantasyLand/Helpful/ApplicativeLaws.php + + - + message: '#^Variable \$ap in PHPDoc tag @var does not match assigned variable \$applicatorX\.$#' + identifier: varTag.differentVariable + count: 1 + path: src/FantasyLand/Helpful/ApplicativeLaws.php + + - + message: '#^Parameter \#1 \$f of function FunctionalPHP\\FantasyLand\\compose expects callable\(FunctionalPHP\\FantasyLand\\Functor\\)\: FunctionalPHP\\FantasyLand\\Functor\, \(callable\(FunctionalPHP\\FantasyLand\\Functor\\)\: FunctionalPHP\\FantasyLand\\Functor\\)\|FunctionalPHP\\FantasyLand\\Functor\ given\.$#' + identifier: argument.type + count: 1 + path: src/FantasyLand/Helpful/FunctorLaws.php + + - + message: '#^Parameter \#2 \$g of function FunctionalPHP\\FantasyLand\\compose expects callable\(FunctionalPHP\\FantasyLand\\Functor\\)\: FunctionalPHP\\FantasyLand\\Functor\, \(callable\(FunctionalPHP\\FantasyLand\\Functor\\)\: FunctionalPHP\\FantasyLand\\Functor\\)\|FunctionalPHP\\FantasyLand\\Functor\ given\.$#' + identifier: argument.type + count: 1 + path: src/FantasyLand/Helpful/FunctorLaws.php + + - + message: '#^Parameter \#4 \$f of static method FunctionalPHP\\FantasyLand\\Helpful\\MonadLaws\:\:test\(\) expects callable\(mixed\)\: FunctionalPHP\\FantasyLand\\Monad\, callable\(a\)\: FunctionalPHP\\FantasyLand\\Useful\\Identity\ given\.$#' + identifier: argument.type + count: 1 + path: src/FantasyLand/Helpful/Tests/MonadLawsTest.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 9228327..babf8c0 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,4 +1,8 @@ +includes: + - phpstan-baseline.neon + parameters: level: 10 + tmpDir: .cache/phpstan paths: - src diff --git a/psalm.baseline.xml b/psalm.baseline.xml new file mode 100644 index 0000000..874eb91 --- /dev/null +++ b/psalm.baseline.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..f930a34 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/FantasyLand/Functor.php b/src/FantasyLand/Functor.php index 84f7b9a..277f0c1 100644 --- a/src/FantasyLand/Functor.php +++ b/src/FantasyLand/Functor.php @@ -18,5 +18,5 @@ interface Functor * * @return Functor */ - public function map(callable $function): Functor; + public function map(callable $function); } diff --git a/src/FantasyLand/Helpful/ApplicativeLaws.php b/src/FantasyLand/Helpful/ApplicativeLaws.php index 5a633e4..e8ef704 100644 --- a/src/FantasyLand/Helpful/ApplicativeLaws.php +++ b/src/FantasyLand/Helpful/ApplicativeLaws.php @@ -52,15 +52,7 @@ public static function test( ); // interchange: u <*> pure x = pure ($ x) <*> u - /** - * @var callable(callable(a): b): b $ap - * - * PHPStan has no idea how currying works and it's not able to infer - * the types of the singly-applied applicator() calls. As such, we'll - * suppress the errors here. - * - * @phpstan-ignore argument.templateType,varTag.differentVariable - */ + /** @var callable(callable(a): b): b $ap */ $applicatorX = applicator($x); $assertEqual( $u->ap($pure($x)), diff --git a/src/FantasyLand/Helpful/FunctorLaws.php b/src/FantasyLand/Helpful/FunctorLaws.php index 2c0dd0c..27ff440 100644 --- a/src/FantasyLand/Helpful/FunctorLaws.php +++ b/src/FantasyLand/Helpful/FunctorLaws.php @@ -30,7 +30,6 @@ public static function test( ): void { $identity = /** - * @template a * @param a $x * @return a */ @@ -48,10 +47,6 @@ static function ($x) { // composition: fmap (f . g) == fmap f . fmap g $assertEqual( map(compose($f, $g), $x), - // PHPStan has no idea how currying works and it's not able to infer - // the types of the singly-applied map() calls. As such, we'll - // suppress the error here. - // @phpstan-ignore argument.type,argument.type compose(map($f), map($g))($x), 'composition' ); diff --git a/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php b/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php index bdbd54a..6768bde 100644 --- a/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/ApplicativeLawsTest.php @@ -35,7 +35,7 @@ public function test_it_should_obey_applicative_laws( // the `pure` function. $pure = /** - * @param a $x + * @param a $x * @return Identity */ function ($x) { diff --git a/src/FantasyLand/Helpful/Tests/MonadLawsTest.php b/src/FantasyLand/Helpful/Tests/MonadLawsTest.php index db18b00..c4e13fc 100644 --- a/src/FantasyLand/Helpful/Tests/MonadLawsTest.php +++ b/src/FantasyLand/Helpful/Tests/MonadLawsTest.php @@ -26,7 +26,7 @@ public function test_if_identity_monad_obeys_the_laws($x, $f, $g): void // the `pure` function. $pure = /** - * @param a $x + * @param a $x * @return Identity */ function ($x) { @@ -37,10 +37,6 @@ function ($x) { [$this, 'assertEquals'], $x, $pure, - // PHPStan seems to have lost track of `a` here, and is unable to - // infer that `$f` should be callable(a): Identity. It seems to - // have inferred that `$f` is callable(mixed): Identity. - // @phpstan-ignore argument.type $f, $g ); @@ -51,10 +47,10 @@ function ($x) { */ public static function provideData(): array { - $addOne = function (int $x) { + $addOne = function (int $x): Identity { return Identity::of($x + 1); }; - $addTwo = function (int $x) { + $addTwo = function (int $x): Identity { return Identity::of($x + 2); }; diff --git a/src/FantasyLand/Useful/Identity.php b/src/FantasyLand/Useful/Identity.php index c66c85b..5635389 100644 --- a/src/FantasyLand/Useful/Identity.php +++ b/src/FantasyLand/Useful/Identity.php @@ -22,7 +22,7 @@ class Identity implements FantasyLand\Monad /** * @inheritdoc * @template A - * @param A $value + * @param A $value * @return Identity */ public static function of($value) @@ -46,16 +46,17 @@ private function __construct($value) */ public function map(callable $transformation): Identity { - /** - * This is a workaround so that static analysis tools can understand - * the type signature of the callables. - * - * @param a $x - * @return Identity - */ - $fn = static function ($x) use ($transformation) { - return Identity::of($transformation($x)); - }; + $fn = + /** + * This is a workaround so that static analysis tools can understand + * the type signature of the callables. + * + * @param a $x + * @return Identity + */ + static function ($x) use ($transformation): Identity { + return Identity::of($transformation($x)); + }; /** @var Identity */ return $this->bind($fn); diff --git a/src/FantasyLand/functions.php b/src/FantasyLand/functions.php index 9f286a6..c76408d 100644 --- a/src/FantasyLand/functions.php +++ b/src/FantasyLand/functions.php @@ -77,10 +77,9 @@ function emptyy(Monoid $a): Monoid * @param callable(a): b $transformation * @param Functor|null $value * - * @return Functor|(callable(Functor): Functor) - * If a functor was provided directly to map, returns the result of - * applying the transformation to the value. Otherwise, returns a curried - * function that expects a functor. + * @return Functor|(callable(Functor): Functor) If a functor was provided directly to map, returns the result + * of applying the transformation to the value. Otherwise, returns + * a curried function that expects a functor. */ function map(callable $transformation, ?Functor $value = null) { @@ -104,18 +103,18 @@ function map(callable $transformation, ?Functor $value = null) * @param callable(a): Monad $function * @param Monad|null $value * - * @return Monad|(callable(Monad): Monad) - * If a monad was provided directly to bind, returns the result. Otherwise, - * returns a curried function that expects a monad. + * @return Monad|(callable(Monad): Monad) If a monad was provided directly to bind, returns the result. + * Otherwise, returns a curried function that expects a monad. */ function bind(callable $function, ?Monad $value = null) { /** @var Monad|(callable(Monad): Monad) */ - return curryN(2, function (callable $function, Monad $value) { + return curryN(2, function (callable $function, Monad $value): Monad { /** * @var callable(a): Monad $function * @var Monad $value */ + /** @var Monad */ return $value->bind($function); })(...func_get_args()); } @@ -155,16 +154,23 @@ function compose(callable $f, callable $g): callable * @param a $x * @param callable(a): b|null $f * - * @return b|(callable(callable(a): b): b) - * If a function was provided directly to applicator, returns the result of - * applying the function to the value. Otherwise, returns a curried - * function that expects said function. + * @return b|(callable(callable(a): b): b) If a function was provided directly to applicator, returns the result of + * applying the function to the value. Otherwise, returns a curried function + * that expects said function. */ function applicator($x, ?callable $f = null) { - return curryN(2, function ($x, callable $f) { - return $f($x); - })(...func_get_args()); + return curryN( + 2, + /** + * @param a $x + * @param callable(a): b $f + * @return b + */ + function ($x, callable $f) { + return $f($x); + } + )(...func_get_args()); } /** @@ -178,13 +184,19 @@ function applicator($x, ?callable $f = null) */ function curryN($numberOfArguments, callable $function, array $args = []) { - return function (...$argsNext) use ($numberOfArguments, $function, $args) { - $argsLeft = $numberOfArguments - func_num_args(); + return + /** + * @param mixed ...$argsNext + * + * @return mixed|callable + */ + function (...$argsNext) use ($numberOfArguments, $function, $args) { + $argsLeft = $numberOfArguments - func_num_args(); - return $argsLeft <= 0 - ? $function(...push_($args, $argsNext)) - : curryN($argsLeft, $function, push_($args, $argsNext)); - }; + return $argsLeft <= 0 + ? $function(...push_($args, $argsNext)) + : curryN($argsLeft, $function, push_($args, $argsNext)); + }; } /**