bit-badger / inspired-by-fsharp
PHP utility classes whose functionality is inspired by their F# counterparts
Requires
- php: >=8.4
Requires (Dev)
- pestphp/pest: ^3.5
- phpoption/phpoption: ^1
This package is not auto-updated.
Last update: 2024-12-19 03:36:53 UTC
README
This project contains PHP utility classes whose functionality is inspired by their F# counterparts.
The v2 series requires at least PHP 8.4. A similar API exists for PHP 8.2 - 8.3 in version 1 of this project; see its README for specifics.
What It Provides
This early-stage library currently provides two classes, both of which are designed to wrap values and indicate the state of the action that produced them. Option<T>
represents a variable that may or may not have a value. Result<TOK, TError>
represents the result of an action; the "ok" and "error" states both provide a value.
Option<T> Replaces null checks | Result<TOK, TError> Replaces exception-based error handling | |
---|---|---|
Creating | ::Some(T) for Some | ::OK(TOK) for OK |
::None() for None | ::Error(TError) for Error | |
::of($value) None if null | ||
Querying | ->isSome: bool | ->isOK: bool |
->isNone: bool | ->isError: bool | |
->contains(T, $strict = true): bool | ->contains(TOK, $strict = true): bool | |
->exists(callable(T): bool): bool | ->exists(callable(TOK): bool): bool | |
Reading | ->value: T | ->ok: TOK |
all throw if called on missing value | ->error: TError | |
Transforming | ->map(callable(T): TMapped): Option<TMapped> | ->map(callable(TOK): TMapped): Result<TMapped, TError> |
all still Option or Result | ->mapError(callable(TError): TMapped): Result<TOK, TMapped> | |
Iterating | ->iter(callable(T): void): void | ->iter(callable(TOK): void): void |
Inspecting returns the original instance | ->tap(callable(Option<T>): void): Option<T> | ->tap(callable(Result<TOK, TError>): void): Result<TOK, TError> |
Continued Processing | ->bind(callable(T): Option<TBound>): Option<TBound> | ->bind(callable(TOK): Result<TBoundOK, TError>): Result<TBoundOK, TError> |
Changing Types | ->toArray(): T[] | ->toArray(): TOK[] |
->toOption(): Option<TOK> |
In addition to this, Option<T>
provides:
->getOrDefault(T)
will return the Some value if it exists or the given default if the option is None.->getOrCall(callable(): mixed)
will call the given function if the option is None. That function may return a value, or may bevoid
ornever
.->getOrThrow(callable(): Exception)
will return the Some value if it exists, or throw the exception returned by the function if the option is None.->filter(callable(T): bool)
will compare a Some value against the callable, and if it returnstrue
, will remain Some; if it returnsfalse
, the value will become None.->unwrap()
will returnnull
for None options and the value for Some options.
Finally, we would be remiss to not acknowledge some really cool prior art in this area - the PhpOption project. Option::of
recognizes their options and converts them properly, and Option<T>
instances have a ->toPhpOption()
method that will convert these back into PhpOption's Some<T>
and None
instances. There is also a ResultType project from the same team, though this project's result does not (yet) have any conversion methods for it.
The Inspiration
F# is an ML-style language that runs under .NET. It has most of the functional programming paradigms, but as it runs on what was designed as an object-oriented runtime - and can use and interoperate with all the .NET libraries - it is a pragmatic approach to functional programming. (Many of its decade+ old features have been implemented into recent versions of C#.)
This library, too, makes some pragmatic choices about structure. In F#, for example, an optional value could be obtained like...
let value =
Option.ofObj myVar
|> Option.map (fun it -> it.Replace("howd", "part"))
|> Option.defaultValue "There was no string"
If myVar
were null
, this value
would have "There was no string"; if myVar
had "howdy", value
would have "party". Each Option
call takes the option as its last parameter, and |>
is the pipeline operator; it provides the previous value as the last parameter to the next operation. A prior version of this library had static functions to mimic this, which resulted in something like...
$value = Option::defaultValue('There was no string',
Option::map(fn($it) => str_replace('howd', 'part', $it),
Option::of($myVar)));
...which reads right-to-left (or bottom-to-top, the way it is formatted there). By implementing these as instance methods, the PHP code looks much cleaner.
$value = Option::of($myVar)
->map(fn($it) => str_replace('howd', 'part', $it))
->getOrDefault('There was no string');
If PHP gets a pipeline operator, we'll revisit lots of stuff here (in a non-breaking way, of course).
Ideas
This library currently has the features which its author needs. To suggest others, reach out to Daniel on the Fediverse at @daniel@fedi.summershome.org or on Twitter at @Bit_Badger.