diff --git a/src/Psl/Option/Option.php b/src/Psl/Option/Option.php index 24300a56..4af0a537 100644 --- a/src/Psl/Option/Option.php +++ b/src/Psl/Option/Option.php @@ -98,9 +98,11 @@ public function unwrap(): mixed * @note: Arguments passed to `Option::unwrapOr()` are eagerly evaluated; * if you are passing the result of a function call, it is recommended to use `Option::unwrapOrElse()`, which is lazily evaluated. * - * @param T $default + * @template O * - * @return T + * @param O $default + * + * @return T|O */ public function unwrapOr(mixed $default): mixed { @@ -114,9 +116,11 @@ public function unwrapOr(mixed $default): mixed /** * Returns the contained some value or computes it from a closure. * - * @param (Closure(): T) $default + * @template O * - * @return T + * @param (Closure(): O) $default + * + * @return T|O */ public function unwrapOrElse(Closure $default): mixed { @@ -215,6 +219,25 @@ public function map(Closure $closure): Option return $this; } + /** + * Maps an `Option` to `Option` by applying a function to a contained value that returns an Option. + * + * @template Tu + * + * @param (Closure(T): Option) $closure + * + * @return Option + */ + public function andThen(Closure $closure): Option + { + if ($this->option !== null) { + return $closure($this->option[0]); + } + + /** @var Option */ + return $this; + } + /** * Applies a function to the contained value (if some), * or returns `Option::some()` with the provided `$default` value (if none). diff --git a/tests/static-analysis/Option/unwrap.php b/tests/static-analysis/Option/unwrap.php new file mode 100644 index 00000000..40172047 --- /dev/null +++ b/tests/static-analysis/Option/unwrap.php @@ -0,0 +1,25 @@ +unwrapOr(null); +} + +function test_none_unwrap_or(): ?string +{ + return Option\none()->unwrapOr(null); +} + +function test_some_unwrap_or_else(): ?string +{ + return Option\some('string')->unwrapOrElse(static fn () => null); +} + +function test_none_unwrap_or_else(): ?string +{ + return Option\none()->unwrapOrElse(static fn() => null); +} diff --git a/tests/unit/Option/NoneTest.php b/tests/unit/Option/NoneTest.php index 41b55dfe..486294dd 100644 --- a/tests/unit/Option/NoneTest.php +++ b/tests/unit/Option/NoneTest.php @@ -101,4 +101,11 @@ public function testMapOrElse(): void static::assertSame(4, $option->mapOrElse(static fn($i) => $i + 1, static fn() => 4)->unwrap()); } + + public function testAndThen(): void + { + $option = Option\none(); + + static::assertNull($option->andThen(static fn($i) => Option\some($i + 1))->unwrapOr(null)); + } } diff --git a/tests/unit/Option/SomeTest.php b/tests/unit/Option/SomeTest.php index 8d032e5f..4b216f12 100644 --- a/tests/unit/Option/SomeTest.php +++ b/tests/unit/Option/SomeTest.php @@ -98,4 +98,11 @@ public function testMapOrElse(): void static::assertSame(3, $option->mapOrElse(static fn($i) => $i + 1, static fn() => 4)->unwrap()); } + + public function testAndThen(): void + { + $option = Option\some(2); + + static::assertSame(3, $option->andThen(static fn($i) => Option\some($i + 1))->unwrapOr(null)); + } }