<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 Result
s
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() |