diff --git a/src/Psl/Option/Option.php b/src/Psl/Option/Option.php index 17b17c07..e852bf59 100644 --- a/src/Psl/Option/Option.php +++ b/src/Psl/Option/Option.php @@ -203,6 +203,30 @@ public function contains(mixed $value): bool return false; } + /** + * Matches the contained option value with the provided closures and returns the result. + * + * @template TNone + * @template TSome + * + * @param (Closure(): TNone) $none A closure to be called when the option is none. + * The closure must not accept any arguments and can return a value. + * Example: `fn() => 'Default value'` + * @param (Closure(T): TSome) $some A closure to be called when the option is some. + * The closure must accept the option value as its only argument and can return a value. + * Example: `fn($value) => $value + 10` + * + * @return TNone|TSome The result of calling the appropriate closure. + */ + public function match(Closure $none, Closure $some): mixed + { + if ($this->option !== null) { + return $some($this->option[0]); + } + + return $none(); + } + /** * Maps an `Option` to `Option` by applying a function to a contained value. * diff --git a/tests/unit/Option/NoneTest.php b/tests/unit/Option/NoneTest.php index 33c4a47a..62e4a953 100644 --- a/tests/unit/Option/NoneTest.php +++ b/tests/unit/Option/NoneTest.php @@ -84,6 +84,26 @@ public function testContains(): void static::assertFalse($option->contains(4)); } + public function testMatch(): void + { + $checked_divisor = static function (int $dividend, int $divisor): Option\Option { + if ($divisor === 0) { + return Option\none(); + } + + return Option\some($dividend / $divisor); + }; + + $try_division = static function (int $dividend, int $divisor) use ($checked_divisor): string { + return $checked_divisor($dividend, $divisor)->match( + none: static fn () => sprintf('%s / %s failed!', $dividend, $divisor), + some: static fn ($value) => sprintf('%s / %s = %s', $dividend, $divisor, $value), + ); + }; + + static::assertSame('2 / 0 failed!', $try_division(2, 0)); + } + public function testMap(): void { $option = Option\none(); diff --git a/tests/unit/Option/SomeTest.php b/tests/unit/Option/SomeTest.php index 7ce1a40f..6229cdb3 100644 --- a/tests/unit/Option/SomeTest.php +++ b/tests/unit/Option/SomeTest.php @@ -81,6 +81,26 @@ public function testContains(): void static::assertTrue($option->contains(2)); } + public function testMatch(): void + { + $checked_divisor = static function (int $dividend, int $divisor): Option\Option { + if ($divisor === 0) { + return Option\none(); + } + + return Option\some($dividend / $divisor); + }; + + $try_division = static function (int $dividend, int $divisor) use ($checked_divisor): string { + return $checked_divisor($dividend, $divisor)->match( + none: static fn () => sprintf('%s / %s failed!', $dividend, $divisor), + some: static fn ($value) => sprintf('%s / %s = %s', $dividend, $divisor, $value), + ); + }; + + static::assertSame('10 / 2 = 5', $try_division(10, 2)); + } + public function testMap(): void { $option = Option\some(2);