Skip to content

feat: OptionとResultにtranspose関数を追加し、型変換をサポート docs: PHPDocを更新し、Closureの型を修正 tests: OptionとResultのtranspose関数に対するユニットテストを追加 #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ parameters:
level: max
paths:
- src
typeAliases:
BasicTypes: 'int|string|bool|null|float|array|iterable|callable|resource|object'
40 changes: 30 additions & 10 deletions src/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,30 @@ public function isNone(): bool;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.is_some_and
*
* @param Closure(T): bool $predicate
*/
public function isSomeAnd(Closure $predicate): bool;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.expect
*
* @return T
* @throws RuntimeException
*/
public function expect(string $message): mixed;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap
*
* @return T
* @throws RuntimeException
*/
public function unwrap(): mixed;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or
*
* @template U
* @param U $default
* @return T|U
Expand All @@ -57,6 +61,7 @@ public function unwrapOr(mixed $default): mixed;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else
*
* @template U
* @param Closure(): U $default
* @return T|U
Expand All @@ -75,13 +80,15 @@ public function unwrapOrThrow(Throwable $exception): mixed;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.inspect
*
* @param Closure(T): mixed $callback
* @return $this
*/
public function inspect(Closure $callback): self;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.and
*
* @template U
* @param Option<U> $right
* @return Option<U>
Expand All @@ -91,6 +98,7 @@ public function and(self $right): self;
/**
* NOTE: PHPdoc's completion by type specification in Closure doesn't work, so I'm redefining it.
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.and_then
*
* @template U
* @param Closure(T): Option<U> $right
* @return Option<U>
Expand All @@ -99,13 +107,16 @@ public function andThen(Closure $right): self;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.or
* @param Option<T> $right
* @return Option<T>
*
* @template U
* @param Option<U> $right
* @return Option<T|U>
*/
public function or(self $right): self;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.or_else
*
* @template U
* @param Closure(): Option<U> $right
* @return Option<T|U>
Expand All @@ -124,20 +135,24 @@ public function orThrow(Throwable $exception): self;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.xor
* @param Option<T> $right
* @return Option<T>
*
* @template U
* @param Option<U> $right
* @return Option<T|U>
*/
public function xor(self $right): self;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.filter
*
* @param Closure(T): bool $predicate
* @return Option<T>
*/
public function filter(Closure $predicate): self;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.map
*
* @template U
* @param Closure(T): U $callback
* @return Option<U>
Expand All @@ -146,19 +161,23 @@ public function map(Closure $callback): self;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.map_or
*
* @template U
* @param Closure(T) :U $callback
* @param U $default
* @return U
* @template V
* @param Closure(T): U $callback
* @param V $default
* @return U|V
*/
public function mapOr(Closure $callback, mixed $default): mixed;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.map_or_else
*
* @template U
* @template V
* @param Closure(T): U $callback
* @param Closure(): U $default
* @return U
* @param Closure(): V $default
* @return U|V
*/
public function mapOrElse(Closure $callback, Closure $default): mixed;

Expand All @@ -172,8 +191,9 @@ public function okOr(mixed $err): Result;

/**
* @see https://doc.rust-lang.org/std/option/enum.Option.html#method.ok_or_else
*
* @template E
* @param Closure() :E $err
* @param Closure(): E $err
* @return Result<T, E>
*/
public function okOrElse(Closure $err): Result;
Expand Down
22 changes: 22 additions & 0 deletions src/Option/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Exception;
use Throwable;
use WizDevelop\PhpMonad\Option;
use WizDevelop\PhpMonad\Result;

use function is_a;

Expand Down Expand Up @@ -103,3 +104,24 @@ function flatten(Option $option): Option
? $option
: $option->unwrap();
}

/**
* Transposes an `Option` of a `Result` into a `Result` of an `Option`.
*
* `None` will be mapped to `Ok(None)`.
* `Some(Ok(_))` and `Some(Err(_))` will be mapped to `Ok(Some(_))` and `Err(_)`.
*
* @template U
* @template E
* @param Option<Result<U, E>> $option
* @return Result<Option<U>, E>
*/
function transpose(Option $option): Result
{
// @phpstan-ignore-next-line
return $option->mapOrElse(
/** @phpstan-ignore-next-line */
static fn (Result $result) => $result->map(Option\some(...)),
static fn () => Result\ok(Option\none()),
);
}
24 changes: 12 additions & 12 deletions src/Result.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ public function isErr(): bool;

/**
* @see https://doc.rust-lang.org/std/result/enum.Result.html#method.is_ok_and
* @param Closure(T) :bool $predicate
* @param Closure(T): bool $predicate
*/
public function isOkAnd(Closure $predicate): bool;

/**
* @see https://doc.rust-lang.org/std/result/enum.Result.html#method.is_err_and
* @param Closure(E) :bool $predicate
* @param Closure(E): bool $predicate
*/
public function isErrAnd(Closure $predicate): bool;

Expand Down Expand Up @@ -72,7 +72,7 @@ public function unwrapOr(mixed $default): mixed;
/**
* @see https://doc.rust-lang.org/std/result/enum.Result.html#method.unwrap_or_else
* @template U
* @param Closure(E) :U $default
* @param Closure(E): U $default
* @return T|U
*/
public function unwrapOrElse(Closure $default): mixed;
Expand All @@ -89,14 +89,14 @@ public function unwrapOrThrow(Throwable $exception): mixed;

/**
* @see https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect
* @param Closure(T) :mixed $callback
* @param Closure(T): mixed $callback
* @return $this
*/
public function inspect(Closure $callback): self;

/**
* @see https://doc.rust-lang.org/std/result/enum.Result.html#method.inspect_err
* @param Closure(E) :mixed $callback
* @param Closure(E): mixed $callback
* @return $this
*/
public function inspectErr(Closure $callback): self;
Expand All @@ -114,8 +114,8 @@ public function and(self $right): self;
* @see https://doc.rust-lang.org/std/result/enum.Result.html#method.and_then
* @template U
* @template F
* @param Closure(T) :Result<U, F> $right
* @return (F is object|resource|array|string|float|int|bool|null ? Result<U, E|F> : Result<U, E>)
* @param Closure(T): Result<U, F> $right
* @return (F is BasicTypes ? Result<U, E|F> : Result<U, E>)
*/
public function andThen(Closure $right): self;

Expand All @@ -130,7 +130,7 @@ public function or(self $right): self;
/**
* @see https://doc.rust-lang.org/std/result/enum.Result.html#method.or_else
* @template F
* @param Closure(E) :Result<T, F> $right
* @param Closure(E): Result<T, F> $right
* @return Result<T, F>
*/
public function orElse(Closure $right): self;
Expand All @@ -148,15 +148,15 @@ public function orThrow(Throwable $exception): self;
/**
* @see https://doc.rust-lang.org/std/result/enum.Result.html#method.map
* @template U
* @param Closure(T) :U $callback
* @param Closure(T): U $callback
* @return Result<U, E>
*/
public function map(Closure $callback): self;

/**
* @see https://doc.rust-lang.org/std/result/enum.Result.html#method.map_err
* @template F
* @param Closure(E) :F $callback
* @param Closure(E): F $callback
* @return Result<T, F>
*/
public function mapErr(Closure $callback): self;
Expand All @@ -173,8 +173,8 @@ public function mapOr(Closure $callback, mixed $default): mixed;
/**
* @see https://doc.rust-lang.org/std/result/enum.Result.html#method.map_or_else
* @template U
* @param Closure(T):U $callback
* @param Closure(E):U $default
* @param Closure(T): U $callback
* @param Closure(E): U $default
* @return U
*/
public function mapOrElse(Closure $callback, Closure $default): mixed;
Expand Down
59 changes: 48 additions & 11 deletions src/Result/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

namespace WizDevelop\PhpMonad\Result;

use Closure;
use Throwable;
use WizDevelop\PhpMonad\Option;
use WizDevelop\PhpMonad\Result;

/**
Expand All @@ -31,6 +33,24 @@ function err(mixed $value): Result\Err
return Result\Err::unit($value);
}

/**
* Creates a Result from a Closure that may throw an exception.
*
* @template T
* @template E
* @param Closure(): T $closure
* @param Closure(Throwable): E $errorHandler
* @return Result<T, E>
*/
function fromThrowable(Closure $closure, Closure $errorHandler): Result
{
try {
return Result\ok($closure());
} catch (Throwable $e) {
return Result\err($errorHandler($e));
}
}

/**
* Converts from `Result<Result<T, E>, E>` to `Result<T, E>`.
*
Expand All @@ -51,19 +71,36 @@ function flatten(Result $result): Result
}

/**
* Creates a Result from a callable that may throw an exception.
* Transposes a `Result` of an `Option` into an `Option` of a `Result`.
*
* @template T
* @template E
* @param callable(): T $callback
* @param callable(Throwable): E $errorHandler
* @return Result<T, E>
* `Ok(None)` will be mapped to `None`.
* `Ok(Some(_))` and `Err(_)` will be mapped to `Some(Ok(_))` and `Some(Err(_))`.
*
* @template U
* @template F
* @param Result<Option<U>, F> $result
* @return Option<Result<U, F>>
*/
function fromThrowable(callable $callback, callable $errorHandler): Result
function transpose(Result $result): Option
{
try {
return ok($callback());
} catch (Throwable $e) {
return err($errorHandler($e));
// @phpstan-ignore return.type
return $result->mapOrElse(
/** @phpstan-ignore-next-line */
static fn (Option $option) => $option->map(Result\ok(...)),
static fn () => Option\some(clone $result),
);
}

/**
* @return Result<bool, non-empty-list<mixed>>
*/
/** @phpstan-ignore-next-line */
function combine(Result ...$results): Result
{
$errs = array_filter($results, static fn (Result $result) => $result->isErr());
if (count($errs) > 0) {
return Result\err(array_values(array_map(static fn (Result $result) => $result->unwrapErr(), $errs)));
}

return Result\ok(true);
}
Loading
Loading