<T, E>
extends |
IteratorAggregate<T> |
|---|
Result<T, E> is the type used for returning and propagating errors. It two
variants, Ok(T), representing success and containing a value, and Err(E),
representing error and containing an error value.
Functions return Result whenever errors are expected and recoverable.
A simple function returning Result might be defined and used like so:
// @param Result<int,string>
function parse_version(string $header): Result {
return match ($header[0] ?? null) {
null => Result\err("invalid header length"),
"1" => Result\ok(1),
"2" => Result\ok(2),
default => Result\err("invalid version"),
};
}
$version = parse_version("1.x");
if ($version->isOk()) {
echo "working with version: {$version->unwrap()}";
} else {
echo "error parsing header: {$version->unwrapErr()}";
}
// @prints working with version: 1
A common problem with using return values to indicate errors is that it is easy
to ignore the return value, thus failing to handle the error. Unused Results
are tracked and will trigger an exception when a Result value is ignored and
goes out of scope. This makes Result especially useful with functions that may
encounter errors but don’t otherwise return a useful value.
// Write $data in $filepath
// @return Result<int,string> The number of bytes that were written to the file if Ok, an error message otherwise
function writeInFile(string $filepath, string $data): Result {
$res = @file_put_contents($filepath, $data);
if ($res === false) {
return Result\err("failed to write in $filepath");
}
return Result\ok($res);
}
writeInFile("/path/to/file", "Hi!");
// @throws TH\Maybe\Result\UnusedResultException Unused Result dropped
Using a Result can be done by calling any method on it, except inspect() & inspectErr().
Note: some methods return another Result that must also be used.
public
isOk(): bool Returns true if the result is the Ok variant.
// @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());
public
isErr(): bool Returns true if the result is the Err variant.
// @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());
public
isOkAnd(callable(T): bool $predicate): bool Returns true if the result is the Ok variant and the value inside of it matches a predicate.
// @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));
public
isErrAnd(callable(E): bool $predicate): bool Returns true if the result is the Err variant and the value inside of it matches a predicate.
// @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));
public
expect(string $message): T 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.
// @var Result<int,string> $x
$x = Result\err("emergency failure");
$x->expect("Testing expect"); // @throws RuntimeException Testing expect
RuntimeException |
public
unwrap(): T Extract the contained value in an Result<T, E> when it is the Ok variant.
Throw a RuntimeException with a generic message if the Result is Err or the contained err value
if it's a \Throwable
// @var Result<int,string> $x
$x = Result\Ok(2);
self::assertEq($x->unwrap(), 2);
// @var Result<int,string> $x
$x = Result\err("emergency failure");
$x->unwrap(); // @throws RuntimeException Unwrapping `Err`: s:17:"emergency failure";
Throwable |
public
unwrapErr(): E Extract the contained value in an Result<T, E> when it is the Err variant.
Throw a RuntimeException with a generic message if the Result is Ok.
// @var Result<int,string> $x
$x = Result\Ok(2);
self::assertEq($x->unwrapErr(), 2); // @throws RuntimeException Unwrapping err on `Ok`: i:2;
RuntimeException |
public
unwrapOr<U>(U $default): T|U Extract the contained value in an Result<T, E> when it is the Ok variant.
Or $default if the Result is Err.
$default = 2;
// @var Result<int,string> $x
$x = Result\Ok(9);
self::assertEq($x->unwrapOr($default), 9);
// @var Result<int,string> $x
$x = Result\err("emergency failure");
self::assertEq($x->unwrapOr($default), $default);
public
unwrapOrElse<U>(callable(E): U $default): T|U Returns the contained Ok value or computes it from a closure.
self::assertEq(Result\ok(2)->unwrapOrElse(strlen(...)), 2);
self::assertEq(Result\err("foo")->unwrapOrElse(strlen(...)), 3);
public
inspect(callable(T): mixed $callback): $this Calls the provided closure with a reference to the contained value (if Ok).
// @return Return<int,string>
function parseInt(string $number): Result {
$int = (int) $number;
if ($number === (string) $int) return Result\ok($int);
return Result\err("could not parse `$number`");
}
$i = parseInt("4")
->inspect(fn (int $x) => printf("original: %d", $x)) // @prints original: 4
->map(fn (int $x) => pow($x, 3))
->expect("failed to parse number");
self::assertEq($i, 64);
public
inspectErr(callable(E): mixed $callback): $this Calls the provided closure with a reference to the contained value (if Err).
// @return Result<string,string>
function readFrom(string $filepath): Result {
$data = @file_get_contents($filepath);
if ($data === false) {
return Result\err("$filepath does not exist");
}
return Result\ok($data);
}
// @return Result<string,string>
function read(): Result {
return readFrom("/not/a/file")
->inspectErr(fn ($e) => printf("failed to read file: %s", $e));
}
read(); // @prints failed to read file: /not/a/file does not exist
public
and<U>(Result<U, E> $right): Result<U, E> Returns $right if the Result is Ok, otherwise returns $this.
// @var Result<int,string> $x
$x = Result\ok(2);
// @var Result<string,string> $y
$y = Result\err("late error");
self::assertEq($x->and($y), Result\err("late error"));
// @var Result<int,string> $x
$x = Result\err("early error");
// @var Result<string,string> $y
$y = Result\ok("foo");
self::assertEq($x->and($y), Result\err("early error"));
// @var Result<int,string> $x
$x = Result\err("not a 2");
// @var Result<string,string> $y
$y = Result\err("late error");
self::assertEq($x->and($y), Result\err("not a 2"));
// @var Result<int,string> $x
$x = Result\ok(2);
// @var Result<string,string> $y
$y = Result\ok("different result type");
self::assertEq($x->and($y), Result\ok("different result type"));
public
andThen<U, F>(callable(T): Result<U, F> $right): Result<U, E|F> Returns Err if the Result is Err, otherwise calls $right with the wrapped value and returns the result.
Often used to chain fallible operations that may return Err.
// @return Result<int,string>
function square(int $x): Result {
$x *= $x;
if (is_int($x)) return Result\ok($x);
return Result\err("overflowed");
}
self::assertEq(Result\ok(2)->andThen(square(...)), Result\ok(4));
self::assertEq(Result\ok(10_000_000_000)->andThen(square(...)), Result\err("overflowed"));
self::assertEq(Result\err("not a number")->andThen(square(...)), Result\err("not a number"));
public
or<F>(Result<T, F> $right): Result<T, F> Returns the Result if it contains a value, otherwise returns $right.
// @var Result<int,string> $x
$x = Result\ok(2);
// @var Result<string,string> $y
$y = Result\err("late error");
self::assertEq($x->or($y), Result\ok(2));
// @var Result<int,string> $x
$x = Result\err("early error");
// @var Result<int,string> $y
$y = Result\ok(2);
self::assertEq($x->or($y), Result\ok(2));
// @var Result<int,string> $x
$x = Result\err("not a 2");
// @var Result<string,string> $y
$y = Result\err("late error");
self::assertEq($x->or($y), Result\err("late error"));
// @var Result<int,string> $x
$x = Result\ok(2);
// @var Result<string,string> $y
$y = Result\ok(100);
self::assertEq($x->or($y), Result\ok(2));
public
orElse<F>(callable(E): Result<T, F> $right): Result<T, F> Returns the Result if it contains a value, otherwise calls $right and returns the result.
// @return Result<int,int>
function sq(int $x): Result { return Result\ok($x * $x); }
// @return Result<int,int>
function err(int $x): Result { return Result\err($x); }
self::assertEq(Result\ok(2)->orElse(sq(...))->orElse(sq(...)), Result\ok(2));
self::assertEq(Result\ok(2)->orElse(err(...))->orElse(sq(...)), Result\ok(2));
self::assertEq(Result\err(3)->orElse(sq(...))->orElse(err(...)), Result\ok(9));
self::assertEq(Result\err(3)->orElse(err(...))->orElse(err(...)), Result\err(3));
public
contains(mixed $value, bool $strict = true): bool Returns true if the Result is a Ok value containing the given value.
// @var Result<int,string> $x
$x = Result\ok(2);
self::assertTrue($x->contains(2));
// @var Result<int,string> $x
$x = Result\ok(3);
self::assertFalse($x->contains(2));
// @var Result<int,string> $x
$x = Result\err("Some error message");
self::assertFalse($x->contains(2));
public
containsErr(mixed $value, bool $strict = true): bool Returns true if the Result is a Ok value containing the given value.
// @var Result<int,string> $x
$x = Result\ok(2);
self::assertFalse($x->containsErr("Some error message"));
// @var Result<int,string> $x
$x = Result\err("Some error message");
self::assertTrue($x->containsErr("Some error message"));
// @var Result<int,string> $x
$x = Result\err("Some other error message");
self::assertFalse($x->containsErr("Some error message"));
public
map<U>(callable(T): U $callback): Result<U, E> Maps an Result<T, E> to Result<U, E> by applying a function to a contained Ok value.
Print the numbers on each line of a string multiplied by two.
// @return Return<int,string>
function parseInt(string $number): Result {
$int = (int) $number;
if ($number === (string) $int) return Result\ok($int);
return Result\err("could not parse `$number`");
}
$input = "1\n2\n3\n4\n";
foreach(explode(PHP_EOL, $input) as $num) {
$n = parseInt($num)->map(fn ($i) => $i * 2);
if ($n->isOk()) {
echo $n->unwrap(), PHP_EOL;
}
}
// @prints 2
// @prints 4
// @prints 6
// @prints 8
public
mapErr<F>(callable(E): F $callback): Result<T, F> Maps an Result<T, E> to Result<T, F> by applying a function to a contained Err value.
public
mapOr<U>(callable(T): U $callback, U $default): U Returns the provided default result (if Err), or applies a function to
the contained value (if Ok).
// @var Result<string,string> $x
$x = Result\ok("foo");
self::assertEq($x->mapOr(strlen(...), 42), 3);
// @var Result<string,string> $x
$x = Result\err("bar");
self::assertEq($x->mapOr(strlen(...), 42), 42);
public
ok(): Option<T> Converts from Result<T, E> to Option<T>, discarding the error, if any.
use TH\Maybe\Option;
// @var Result<int,string> $x
$x = Result\ok(2);
self::assertEq($x->ok(), Option\some(2));
// @var Result<int,string> $x
$x = Result\err("Nothing here");
self::assertEq($x->ok(), Option\none());
public
err(): Option<E> Converts from Result<T, E> to Option<E>, discarding the success value, if any.
use TH\Maybe\Option;
// @var Result<int,string> $x
$x = Result\ok(2);
self::assertEq($x->err(), Option\none());
// @var Result<int,string> $x
$x = Result\err("Nothing here");
self::assertEq($x->err(), Option\some("Nothing here"));
public
mapOrElse<U>(callable(T): U $callback, callable(E): U $default): U Computes a default function result (if Err), or applies a different
function to the contained value (if Ok).
This function can be used to unpack a successful result while handling an error.
$k = 21;
// @var Result<string,string> $x
$x = Result\ok("foo");
self::assertEq($x->mapOrElse(strlen(...), fn ($e) => $k * 2), 3);
// @var Result<string,string> $x
$x = Result\err("bar");
self::assertEq($x->mapOrElse(strlen(...), fn ($e) => $k * 2), 42);
| Methods inherited from IteratorAggregate |
|---|
getIterator() |