Skip to content

Commit

Permalink
feature: add Result::is* & Option::is*
Browse files Browse the repository at this point in the history
  • Loading branch information
mathroc committed Jul 4, 2023
1 parent 15213b0 commit 8e1808c
Show file tree
Hide file tree
Showing 15 changed files with 542 additions and 41 deletions.
10 changes: 10 additions & 0 deletions psalm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@
<directory name="tests/Unit" />
</errorLevel>
</MoreSpecificReturnType>
<NoValue>
<errorLevel type="suppress">
<directory name="tests/Unit" />
</errorLevel>
</NoValue>
<UnusedClosureParam>
<errorLevel type="suppress">
<directory name="tests/Unit" />
</errorLevel>
</UnusedClosureParam>
<PropertyNotSetInConstructor>
<errorLevel type="suppress">
<directory name="tests/Unit" />
Expand Down
79 changes: 79 additions & 0 deletions src/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,75 @@
// #[ExamplesSetup(IgnoreUnusedResults::class)]
interface Option extends \IteratorAggregate
{
/**
* Returns `true` if the option is the `Some` variant.
*
* # Examples
*
* ```
* // @var Option<int,string> $x
* $x = Option\Some(2);
* self::assertTrue($x->isSome());
* ```
*
* ```
* // @var Option<int,string> $x
* $x = Option\none();
* self::assertFalse($x->isSome());
* ```
*
* @psalm-assert-if-true Option\Some $this
* @psalm-assert-if-false Option\None $this
*/
public function isSome(): bool;

/**
* Returns `true` if the option is the `None` variant.
*
* # Examples
*
* ```
* // @var Option<int,string> $x
* $x = Option\Some(2);
* self::assertFalse($x->isNone());
* ```
*
* ```
* // @var Option<int,string> $x
* $x = Option\none();
* self::assertTrue($x->isNone());
* ```
*
* @psalm-assert-if-true Option\None $this
* @psalm-assert-if-false Option\Some $this
*/
public function isNone(): bool;

/**
* Returns `true` if the option is the `Some` variant and the value inside of it matches a predicate.
*
* @param callable(T):bool $predicate
*
* @psalm-assert-if-true Option\Some $this
*
* # Examples
*
* ```
* // @var Option<int,string> $x
* $x = Option\Some(2);
* self::assertTrue($x->isSomeAnd(fn ($n) => $n < 5));
* self::assertFalse($x->isSomeAnd(fn ($n) => $n > 5));
* ```
*
* ```
* // @var Option<int,string> $x
* $x = Option\none();
* self::assertFalse($x->isSomeAnd(fn ($n) => $n < 5));
* self::assertFalse($x->isSomeAnd(fn ($n) => $n > 5));
* ```
*/
public function isSomeAnd(callable $predicate): bool;

/**
* Extract the contained value in an `Option<T>` when it is the `Some` variant.
* Throw a `RuntimeException` with a custum provided message if the `Option` is `None`.
Expand All @@ -72,6 +141,8 @@ interface Option extends \IteratorAggregate
*
* @return T
* @throws \RuntimeException
*
* @psalm-assert Option\Some $this
*/
public function expect(string $message): mixed;

Expand All @@ -95,6 +166,8 @@ public function expect(string $message): mixed;
*
* @return T
* @throws \RuntimeException
*
* @psalm-assert Option\Some $this
*/
public function unwrap(): mixed;

Expand Down Expand Up @@ -231,6 +304,8 @@ public function andThen(callable $right): Option;
*
* @param Option<T> $right
* @return Option<T>
*
* @psalm-assert-if-false Option\None $this
*/
public function or(Option $right): Option;

Expand All @@ -257,6 +332,8 @@ public function or(Option $right): Option;
*
* @param callable():Option<T> $right
* @return Option<T>
*
* @psalm-assert-if-false Option\None $this
*/
public function orElse(callable $right): Option;

Expand Down Expand Up @@ -303,6 +380,8 @@ public function xor(Option $right): Option;
* $x = Option\none();
* self::assertFalse($x->contains(2));
* ```
*
* @psalm-assert-if-true Option\Some $this
*/
public function contains(mixed $value, bool $strict = true): bool;

Expand Down
24 changes: 24 additions & 0 deletions src/Option/None.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,30 @@ enum None implements Option
{
case instance;

/**
* @return false
*/
public function isSome(): bool
{
return false;
}

/**
* @return true
*/
public function isNone(): bool
{
return true;
}

/**
* @return false
*/
public function isSomeAnd(callable $predicate): bool
{
return false;
}

/**
* @throws \RuntimeException
*/
Expand Down
25 changes: 23 additions & 2 deletions src/Option/Some.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,43 @@
*/
final class Some implements Option
{
/**
* @return true
*/
public function isSome(): bool
{
return true;
}

/**
* @return false
*/
public function isNone(): bool
{
return false;
}

public function isSomeAnd(callable $predicate): bool
{
return $predicate($this->value);
}

/**
* @param T $value
* @nodoc
*/
public function __construct(private mixed $value) {}

/**
* @throws void
* @phpstan-throws void
*/
public function expect(string $message): mixed
{
return $this->value;
}

/**
* @throws void
* @phpstan-throws void
*/
public function unwrap(): mixed
{
Expand Down
104 changes: 104 additions & 0 deletions src/Result.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,100 @@
*/
interface Result extends \IteratorAggregate
{
/**
* Returns `true` if the result is the `Ok` variant.
*
* # Examples
*
* ```
* // @var Result<int,string> $x
* $x = Result\ok(2);
* self::assertTrue($x->isOk());
* ```
*
* ```
* // @var Result<int,string> $x
* $x = Result\err(2);
* self::assertFalse($x->isOk());
* ```
*
* @psalm-assert-if-true Result\Ok $this
* @psalm-assert-if-false Result\Err $this
*/
public function isOk(): bool;

/**
* Returns `true` if the result is the `Err` variant.
*
* # Examples
*
* ```
* // @var Result<int,string> $x
* $x = Result\ok(2);
* self::assertFalse($x->isErr());
* ```
*
* ```
* // @var Result<int,string> $x
* $x = Result\err(2);
* self::assertTrue($x->isErr());
* ```
*
* @psalm-assert-if-true Result\Err $this
* @psalm-assert-if-false Result\Ok $this
*/
public function isErr(): bool;

/**
* Returns `true` if the result is the `Ok` variant and the value inside of it matches a predicate.
*
* # Examples
*
* ```
* // @var Result<int,string> $x
* $x = Result\ok(2);
* self::assertTrue($x->isOkAnd(fn ($n) => $n < 5));
* self::assertFalse($x->isOkAnd(fn ($n) => $n > 5));
* ```
*
* ```
* // @var Result<int,string> $x
* $x = Result\err(2);
* self::assertFalse($x->isOkAnd(fn ($n) => $n < 5));
* self::assertFalse($x->isOkAnd(fn ($n) => $n > 5));
* ```
*
* @param callable(T):bool $predicate
*
* @psalm-assert-if-true Result\Ok $this
*/
public function isOkAnd(callable $predicate): bool;

/**
* Returns `true` if the result is the `Err` variant and the value inside of it matches a predicate.
*
* # Examples
*
* ```
* // @var Result<int,string> $x
* $x = Result\err(2);
* self::assertTrue($x->isErrAnd(fn ($n) => $n < 5));
* self::assertFalse($x->isErrAnd(fn ($n) => $n > 5));
* ```
*
* ```
* // @var Result<int,string> $x
* $x = Result\ok(2);
* self::assertFalse($x->isErrAnd(fn ($n) => $n < 5));
* self::assertFalse($x->isErrAnd(fn ($n) => $n > 5));
* ```
*
* @param callable(E):bool $predicate
*
* @psalm-assert-if-true Result\Err $this
*/
public function isErrAnd(callable $predicate): bool;

/**
* Extract the contained value in an `Result<T, E>` when it is the `Ok` variant.
* Throw a `RuntimeException` with a custum provided message if the `Result` is `Err`.
Expand All @@ -84,6 +178,8 @@ interface Result extends \IteratorAggregate
*
* @return T
* @throws \RuntimeException
*
* @psalm-assert Result\Ok $this
*/
public function expect(string $message): mixed;

Expand All @@ -108,6 +204,8 @@ public function expect(string $message): mixed;
*
* @return T
* @throws \Throwable
*
* @psalm-assert Result\Ok $this
*/
public function unwrap(): mixed;

Expand All @@ -125,6 +223,8 @@ public function unwrap(): mixed;
*
* @return E
* @throws \RuntimeException
*
* @psalm-assert Result\Err $this
*/
public function unwrapErr(): mixed;

Expand Down Expand Up @@ -371,6 +471,8 @@ public function orElse(callable $right): Result;
* $x = Result\err("Some error message");
* self::assertFalse($x->contains(2));
* ```
*
* @psalm-assert-if-true Result\Ok $this
*/
public function contains(mixed $value, bool $strict = true): bool;

Expand All @@ -392,6 +494,8 @@ public function contains(mixed $value, bool $strict = true): bool;
* $x = Result\err("Some other error message");
* self::assertFalse($x->containsErr("Some error message"));
* ```
*
* @psalm-assert-if-true Result\Err $this
*/
public function containsErr(mixed $value, bool $strict = true): bool;

Expand Down
Loading

0 comments on commit 8e1808c

Please sign in to comment.