2026-01-25 22:54:25 -05:00
2026-01-25 22:54:25 -05:00
2024-11-20 22:01:40 -05:00
2026-01-25 22:54:25 -05:00
2026-01-25 22:54:25 -05:00
2024-07-26 18:13:27 +00:00
2024-11-20 22:01:40 -05:00
2026-01-25 22:54:25 -05:00

Inspired by F#

This project contains PHP utility classes whose functionality is inspired by their F# counterparts.

The v3 series requires at least PHP 8.5, and includes pipeable array_* functions. The v2 series requires at least PHP 8.4. A similar v2 API exists for PHP 8.2 - 8.3 in version 1 of this project; see its README for specifics.

What It Provides

This library currently provides three classes.

Options and Results

Two of them 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 be void or never.
  • ->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 returns true, will remain Some; if it returns false, the value will become None.
  • ->unwrap() will return null for None options and the value for Some options.

We would be remiss to not acknowledge some excellent 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.

Pipeable array_* Functions

The final class, exclusive to v3, is Arr, which wraps the array_* functions with versions that return a single-arity (AKA "one parameter") function which takes the array and returns the result.

The array_map example is easy to understand. This function requires the array first, and the transformation callback second. This does not play nicely with PHP 8.5's pipeline operator (|>); to pipe to array_map, one has to create an anonymous function to reverse the parameters. Arr::map() does this for you.

// The result of both calls is [2, 4, 6]

// Without this library
$withoutLibrary =
    [1, 2, 3]
    |> fn ($arr) => array_map($arr, fn ($x) => x * 2);
    
// With this library
$withLibrary =
    [1, 2, 3]
    |> Arr::map(fn ($x) => $x * 2);

All array_* functions which expect the target array as the first parameter have a matching Arr::* function which provides a version of the function with the array last. array_* functions which only take the array (such as array_count_values or array_keys) have corresponding functions in Arr for completeness; when piping to these, call the Arr function vs. using it as a callable.

// Use this
$theKeys = $array |> Arr::keys();
// ...or this
$theKeys = $array |> array_keys(...);

// Not this
$theKeys = $array |> Arr::keys(...);

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 (TODO: !!!!), 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.

Description
PHP utility classes whose functionality is inspired by their F# counterparts
Readme 221 KiB
v2.0.0 Latest
2024-11-21 03:21:19 +00:00
Languages
PHP 100%