Add details to README
This commit is contained in:
parent
5a8a41a660
commit
efb3a4461e
64
README.md
64
README.md
|
@ -1,3 +1,63 @@
|
||||||
# inspired-by-fsharp
|
# Inspired by F#
|
||||||
|
|
||||||
PHP utility classes whose functionality is inspired by their F# counterparts
|
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<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>`<br>Replaces `null` checks | `Result<TOK, TError>`<br>Replaces exception-based error handling |
|
||||||
|
|----------------------------------------------------|--------------------------------------------------------------------------------|------------------------------------------------------------------|
|
||||||
|
| **Creating** | `::Some(T)` for Some<br>`::None()` for None<br>`::of($value)` _None if `null`_ | `::OK(TOK)` for OK<br>`::Error(TError)` for Error |
|
||||||
|
| **Querying** | `->isSome()`<br>`->isNone()` | `->isOK()`<br>`->isError()` |
|
||||||
|
| **Reading**<br>_throws if called on missing value_ | `->get()` | `->getOK()`<br>`->getError()` |
|
||||||
|
| **Transforming**<br>_still `Option` or `Result`_ | `->map(callable(T): U)` | `->map(callable(TOK): U)`<br>`->mapError(callable(TError): U)` |
|
||||||
|
| **Iterating** | `->iter(callable(T): void)` | `->iter(callable(TOK): void)` |
|
||||||
|
| **Inspecting**<br>_returns the original instance_ | `->tap(callable(Option<T>): void)` | `->tap(callable(Result<TOK, TError>): void)` |
|
||||||
|
|
||||||
|
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`.
|
||||||
|
- `->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<TOK, TError>` 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<T>` instances have a `->toPhpOption()` method that will convert these back into PhpOption's `Some<T>` 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.
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
/**
|
/**
|
||||||
* @author Daniel J. Summers <daniel@bitbadger.solutions>
|
* @author Daniel J. Summers <daniel@bitbadger.solutions>
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
@ -19,8 +20,7 @@ use InvalidArgumentException;
|
||||||
* `map` and `iter` become de facto conditional operators).
|
* `map` and `iter` become de facto conditional operators).
|
||||||
*
|
*
|
||||||
* `Option::Some(T)` and `Option::None()` create instances. `get()` is available on options, but will throw an exception
|
* `Option::Some(T)` and `Option::None()` create instances. `get()` is available on options, but will throw an exception
|
||||||
* if called on a `None` option. The remaining functions are statically available, and should be provided an `Option`
|
* if called on a `None` option.
|
||||||
* instance as their final parameter.
|
|
||||||
*
|
*
|
||||||
* @template T The type of value represented by this option
|
* @template T The type of value represented by this option
|
||||||
*/
|
*/
|
||||||
|
@ -45,7 +45,7 @@ readonly class Option
|
||||||
public function get(): mixed
|
public function get(): mixed
|
||||||
{
|
{
|
||||||
return match (true) {
|
return match (true) {
|
||||||
self::isSome($this) => $this->value,
|
$this->isSome() => $this->value,
|
||||||
default => throw new InvalidArgumentException('Cannot get the value of a None option'),
|
default => throw new InvalidArgumentException('Cannot get the value of a None option'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
/**
|
/**
|
||||||
* @author Daniel J. Summers <daniel@bitbadger.solutions>
|
* @author Daniel J. Summers <daniel@bitbadger.solutions>
|
||||||
* @license MIT
|
* @license MIT
|
||||||
|
* @since 1.0.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user