diff --git a/src/Option.php b/src/Option.php index 429f6fb..74960f2 100644 --- a/src/Option.php +++ b/src/Option.php @@ -202,4 +202,17 @@ readonly class Option { return self::isSome($it) ? $it->get() : null; } + + /** + * Tap into the `Result` for a secondary action, returning the result + * + * @param callable(Option): mixed $f The function to run (return value is ignored) + * @param Option $it The option against which the function should be run + * @return Option The same option provided + */ + public static function tap(callable $f, Option $it): Option + { + $f($it); + return $it; + } } diff --git a/src/Result.php b/src/Result.php index 9da7e21..df44dac 100644 --- a/src/Result.php +++ b/src/Result.php @@ -165,4 +165,17 @@ readonly class Result { return Result::isOK($it) ? Option::Some($it->getOK()) : Option::None(); } + + /** + * Tap into the `Result` for a secondary action, returning the result + * + * @param callable(Result): mixed $f The function to run (return value is ignored) + * @param Result $it The result against which the function should be run + * @return Result The same result provided + */ + public static function tap(callable $f, Result $it): Result + { + $f($it); + return $it; + } } diff --git a/tests/OptionTest.php b/tests/OptionTest.php index 8ca6de6..d183932 100644 --- a/tests/OptionTest.php +++ b/tests/OptionTest.php @@ -237,4 +237,26 @@ class OptionTest extends TestCase { $this->assertEquals('boy howdy', Option::unwrap(Option::Some('boy howdy')), '"Some" should return its value'); } + + #[TestDox('Tap succeeds with Some')] + public function testTapSucceedsWithSome(): void + { + $value = ''; + $original = Option::Some('testing'); + $tapped = Option::tap( + function (Option $it) use (&$value) { $value = Option::isSome($it) ? $it->get() : 'none'; }, $original); + $this->assertEquals('testing', $value, 'The tapped function was not called'); + $this->assertSame($original, $tapped, 'The same option should have been returned'); + } + + #[TestDox('Tap succeeds with None')] + public function testTapSucceedsWithNone(): void + { + $value = ''; + $original = Option::None(); + $tapped = Option::tap( + function (Option $it) use (&$value) { $value = Option::isSome($it) ? $it->get() : 'none'; }, $original); + $this->assertEquals('none', $value, 'The tapped function was not called'); + $this->assertSame($original, $tapped, 'The same option should have been returned'); + } } diff --git a/tests/ResultTest.php b/tests/ResultTest.php index 115626e..cad5151 100644 --- a/tests/ResultTest.php +++ b/tests/ResultTest.php @@ -197,4 +197,28 @@ class ResultTest extends TestCase $value = Result::toOption(Result::Error('file not found')); $this->assertTrue(Option::isNone($value), 'An "Error" result should map to a "None" option'); } + + #[TestDox('Tap succeeds for OK result')] + public function testTapSucceedsForOKResult(): void + { + $value = ''; + $original = Result::OK('working'); + $tapped = Result::tap(function (Result $it) use (&$value) { + $value = Result::isOK($it) ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError(); + }, $original); + $this->assertEquals('OK: working', $value, 'The tapped function was not called'); + $this->assertSame($original, $tapped, 'The same result should have been returned'); + } + + #[TestDox('Tap succeeds for Error result')] + public function testTapSucceedsForErrorResult(): void + { + $value = ''; + $original = Result::Error('failed'); + $tapped = Result::tap(function (Result $it) use (&$value) { + $value = Result::isOK($it) ? 'OK: ' . $it->getOK() : 'Error: ' . $it->getError(); + }, $original); + $this->assertEquals('Error: failed', $value, 'The tapped function was not called'); + $this->assertSame($original, $tapped, 'The same result should have been returned'); + } }