Interface TH\Maybe\Result <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
Results must be used

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.

Methods

public isOk(): bool

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());

public isErr(): 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());

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.

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));

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.

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));

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.

Examples

// @var Result<int,string> $x
$x = Result\err("emergency failure");
$x->expect("Testing expect"); // @throws RuntimeException Testing expect

Throws

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

Examples

// @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";

Throws

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.

Examples

// @var Result<int,string> $x
$x = Result\Ok(2);
self::assertEq($x->unwrapErr(), 2); // @throws RuntimeException Unwrapping err on `Ok`: i:2;

Throws

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.

Examples

$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.

Examples

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).

Examples

// @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).

Examples

// @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.

Examples

// @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.

Examples

// @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.

Examples

// @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.

Examples

// @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.

Examples

// @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.

Examples

// @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.

Examples

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.

Examples

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).

Examples

// @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.

Examples

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.

Examples

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.

Examples

$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()