# Inspired by F# This project contains PHP utility classes whose functionality is inspired by their F# counterparts. ## 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` represents a variable that may or may not have a value. `Result` represents the result of an action; the "ok" and "error" states both provide a value. | | `Option`
Replaces `null` checks | `Result`
Replaces exception-based error handling | |----------------------------------------------------|--------------------------------------------------------------------------------|-----------------------------------------------------------------------------| | **Creating** | `::Some(T)` for Some
`::None()` for None
`::of($value)` _None if `null`_ | `::OK(TOK)` for OK
`::Error(TError)` for Error | | **Querying** | `->isSome(): bool`
`->isNone(): bool` | `->isOK(): bool`
`->isError(): bool` | | **Reading**
_throws if called on missing value_ | `->get(): T` | `->getOK(): TOK`
`->getError(): TError` | | **Transforming**
_still `Option` or `Result`_ | `->map(callable(T): U): U` | `->map(callable(TOK): U): U`
`->mapError(callable(TError): U): U` | | **Iterating** | `->iter(callable(T): void): void` | `->iter(callable(TOK): void): void` | | **Inspecting**
_returns the original instance_ | `->tap(callable(Option): void): void` | `->tap(callable(Result): void): void` | | **Continued Processing** | `->bind(callable(T): Option): Option` | `->bind(callable(TOK): Result): Result` | In addition to this, `Option` 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`. - `->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. - `->is(T, $strict = true)` will return `true` if the option is Some and the value matches. Strict equality (the default) uses `===` for the comparison; if strict is set to `false`, the comparison will use `==` instead. - `->unwrap()` will return `null` for None options and the value for Some options. `Result` also provides: - `toOption()` will transform an OK result to a Some option, and an Error result to a None option. Finally, we would be remiss to not acknowledge some really cool prior art in this area - the [PhpOption](https://github.com/schmittjoh/php-option) project. `Option::of` recognizes their options and converts them properly, and `Option` instances have a `->toPhpOption()` method that will convert these back into PhpOption's `Some` and `None` instances. There is also a [ResultType](https://github.com/GrahamCampbell/Result-Type) project from the same team, though this project's result does not (yet) have any conversion methods for it. ## The Inspiration [F#](https://fsharp.org/) 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... ```fsharp 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... ```php $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. ```php $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.