Skip to content

Instantly share code, notes, and snippets.

@tcarrio
Created May 8, 2024 01:51
Show Gist options
  • Save tcarrio/301deeba46a0353a41c13bd1068845cc to your computer and use it in GitHub Desktop.
Save tcarrio/301deeba46a0353a41c13bd1068845cc to your computer and use it in GitHub Desktop.
Rusty Option-like operations in PHP

Rusty Option-like operations in PHP

This is very half-baked, but I'm capturing an idea I wrote in an empty buffer.

This provides a similar behavior to Rust's std::option::Option enum which encapsulates a value either existing as it should as "Some" value, or being empty or "None".

There's also a convenience for pattern matching, though more would need to go into the typing with PHPStan docstrings to make it more dynamic. As it is now though, it's possible to act on a Some or None match with a closure for the items.

Future ideas

The same approach could be applied with pattern's like std::result::Result that wraps something as a successful operation or "Ok" or an "Err"or.

<?php
use Exception;
/**
* @template T
*/
class Boxed
{
/**
* @param T $value
*/
private function __construct(public readonly $value)
{
}
}
interface IResult
{
public function isSome(): boolean;
public function isNone(): boolean;
}
trait ResultTrait
{
public function isSome(): boolean
{
return false;
}
public function isNone(): boolean
{
return false;
}
}
/**
* @template T = never
* @extends Boxed<T>
*/
class Some extends Boxed implements IResult
{
use ResultTrait;
public function isSome(): boolean
{
return true;
}
}
/**
* @extends Boxed<never>
*/
class None extends Boxed implements IResult
{
use ResultTrait;
public function isNone(): boolean
{
return true;
}
}
class InvalidResultException extends Exception
{
}
class Result
{
private static const NONE = new None();
public static function from(...$values): Some | None
{
if (count($values) > 0) {
return new Some($values);
}
return self::NONE;
}
public static function match(IResult $result, callable $someFn, callable $noneFn, ?callable $invalidFn = null)
{
return match ($result instanceof IResult) {
$result->isSome() => call_user_func($someFn, $result->value),
$result->isNone() => call_user_func($noneFn),
_ => is_null($invalidFn) ? throw new InvalidResultException($result) : $invalidFn($result),
};
}
}
function testIt(): void
{
$total = 99;
$result = Result::from(42);
$total += match ($result instanceof IResult) {
$result->isSome() => $result->value,
$result->isNone() => 0,
_ => throw new InvalidResultException($result),
};
// OR
$total += Result::match($result,
fn ($some) => $some,
fn () => 0,
);
// OR handling invalid results safely
$total += Result::match($result,
fn ($some) => $some,
fn () => 0,
fn () => 0,
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment