From 40a4574e9d278b833481fee8df2e712c6a038c07 Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Mon, 28 Sep 2020 15:57:00 +0200 Subject: [PATCH] [Fun] Add pipe and after combinators (#57) --- src/Psl/Fun/after.php | 41 +++++++++++++++++++++++++++++++++++ src/Psl/Fun/pipe.php | 43 +++++++++++++++++++++++++++++++++++++ src/Psl/Internal/Loader.php | 2 ++ tests/Psl/Fun/AfterTest.php | 32 +++++++++++++++++++++++++++ tests/Psl/Fun/PipeTest.php | 40 ++++++++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+) create mode 100644 src/Psl/Fun/after.php create mode 100644 src/Psl/Fun/pipe.php create mode 100644 tests/Psl/Fun/AfterTest.php create mode 100644 tests/Psl/Fun/PipeTest.php diff --git a/src/Psl/Fun/after.php b/src/Psl/Fun/after.php new file mode 100644 index 0000000..38b8a03 --- /dev/null +++ b/src/Psl/Fun/after.php @@ -0,0 +1,41 @@ + $i + 1, + * fn(int $i) => $i * 5 + * ); + * => $runExpression(1) === 10; + * => $runExpression(2) === 15; + * => $runExpression(3) === 20; + * + * $greet = Fun\after( + * fn($value) => 'Hello' . $value, + * fn($value) => $value . '!' + * ); + * => $greet('World') === 'Hello World!'; + * => $greet('Jim') === 'Hello Jim!'; + * + * @template I + * @template O + * @template R + * + * @psalm-param callable(I): O $first + * @psalm-param callable(O): R $next + * + * @psalm-return callable(I): R + * + * @psalm-pure + */ +function after(callable $first, callable $next): callable +{ + return static fn ($input) => $next($first($input)); +} diff --git a/src/Psl/Fun/pipe.php b/src/Psl/Fun/pipe.php new file mode 100644 index 0000000..c8cf662 --- /dev/null +++ b/src/Psl/Fun/pipe.php @@ -0,0 +1,43 @@ + 'Hello' . $value, + * fn($value) => $value . '!', + * fn($value) => '¡' . $value + * ); + * => $greet('World') === '¡Hello World!'; + * => $greet('Jim') === '¡Hello Jim!'; + * + * @template T + * + * @psalm-param callable(T): T ...$stages + * + * @psalm-return callable(T): T + * + * @psalm-pure + */ +function pipe(callable ...$stages): callable +{ + return static fn ($input) => reduce( + $stages, + /** + * @template IO + * + * @param IO $input + * @param callable(IO): IO $next + * + * @return IO + */ + static fn ($input, int $key, callable $next) => $next($input), + $input + ); +} diff --git a/src/Psl/Internal/Loader.php b/src/Psl/Internal/Loader.php index c3a6fd5..c74ac38 100644 --- a/src/Psl/Internal/Loader.php +++ b/src/Psl/Internal/Loader.php @@ -95,6 +95,8 @@ final class Loader 'Psl\Arr\map', 'Psl\Arr\map_keys', 'Psl\Arr\map_with_key', + 'Psl\Fun\after', + 'Psl\Fun\pipe', 'Psl\Asio\wrap', 'Psl\Internal\boolean', 'Psl\Internal\type', diff --git a/tests/Psl/Fun/AfterTest.php b/tests/Psl/Fun/AfterTest.php new file mode 100644 index 0000000..5e27dcd --- /dev/null +++ b/tests/Psl/Fun/AfterTest.php @@ -0,0 +1,32 @@ + $x . ' world', + fn (string $z): string => $z . '!!' + ); + + self::assertSame('Hello world!!', $x('Hello')); + } + + public function testItCombinesAFunctionThatDealWithDifferentTypes(): void + { + $x = Fun\after( + fn (string $x): int => Str\length($x), + fn (int $z): string => $z . '!' + ); + + self::assertSame('5!', $x('Hello')); + } +} diff --git a/tests/Psl/Fun/PipeTest.php b/tests/Psl/Fun/PipeTest.php new file mode 100644 index 0000000..b98c7f6 --- /dev/null +++ b/tests/Psl/Fun/PipeTest.php @@ -0,0 +1,40 @@ + $x . ' world', + fn (string $y): string => $y . '?', + fn (string $z): string => $z . '!', + ); + + self::assertSame('Hello world?!', $x('Hello')); + } + + public function testItCombinesMultipleFunctionsThatDealWithDifferentTypes(): void + { + $x = Fun\pipe( + fn (string $x): int => Str\length($x), + fn (int $y): string => $y . '!' + ); + + self::assertSame('5!', $x('Hello')); + } + + public function testItCanCreateAnEmptyCombination(): void + { + $x = Fun\pipe(); + + self::assertSame('Hello', $x('Hello')); + } +}