-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* develop: specify next release use psalm 5 fix tests add Fold add documentation allow to map results and failures add fold monad
- Loading branch information
Showing
25 changed files
with
904 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,11 @@ | ||
# Changelog | ||
|
||
## 4.11.0 - 2023-02-18 | ||
|
||
### Added | ||
|
||
- `Innmind\Immutable\Fold` | ||
|
||
## 4.10.0 - 2023-02-05 | ||
|
||
### Added | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
# `Fold` | ||
|
||
The `Fold` monad is intented to work with _(infinte) stream of data_ by folding each element to a single value. This monad distinguishes between the type used to fold and the result type, this allows to inform the _stream_ that it's no longer necessary to extract elements as the folding is done. | ||
|
||
An example is reading from a socket as it's an infinite stream of strings: | ||
|
||
```php | ||
$socket = \stream_socket_client(/* args */); | ||
/** @var Fold<string, list<string>, list<string>> */ | ||
$fold = Fold::with([]); | ||
|
||
do { | ||
// production code should wait for the socket to be "ready" | ||
$line = \fgets($socket); | ||
|
||
if ($line === false) { | ||
$fold = Fold::fail('socket not readable'); | ||
} | ||
|
||
$fold = $fold | ||
->map(static fn($lines) => \array_merge($lines, [$line])) | ||
->flatMap(static fn($lines) => match (\end($lines)) { | ||
"quit\n" => Fold::result($lines), | ||
default => Fold::with($lines), | ||
}); | ||
$continue = $fold->match( | ||
static fn() => true, // still folding | ||
static fn() => false, // got a result so stop | ||
static fn() => false, // got a failure so stop | ||
); | ||
} while ($continue); | ||
|
||
$fold->match( | ||
static fn() => null, // unreachable in this case because no more folding outside the loop | ||
static fn($lines) => \var_dump($lines), | ||
static fn($failure) => throw new \RuntimeException($failure), | ||
); | ||
``` | ||
|
||
This example will read all lines from the socket until one line contains `quit\n` then the loop will stop and either dump all the lines to the output or `throw new RuntimeException('socket not reachable')`. | ||
|
||
## `::with()` | ||
|
||
This named constructor accepts a value with the notion that more elements are necessary to compute a result | ||
|
||
## `::result()` | ||
|
||
This named constructor accepts a _result_ value meaning that folding is finished. | ||
|
||
## `::fail()` | ||
|
||
This named constructor accepts a _failure_ value meaning that the folding operation failed and no _result_ will be reachable. | ||
|
||
## `->map()` | ||
|
||
This method allows to transform the value being folded. | ||
|
||
```php | ||
$fold = Fold::with([])->map(static fn(array $folding) => new \ArrayObject($folding)); | ||
``` | ||
|
||
## `->flatMap()` | ||
|
||
This method allows to both change the value and the _state_, for example switching from _folding_ to _result_. | ||
|
||
```php | ||
$someElement = /* some data */; | ||
$fold = Fold::with([])->flatMap(static fn($elements) => match ($someElement) { | ||
'finish' => Fold::result($elements), | ||
default => Fold::with(\array_merge($elements, [$someElement])), | ||
}); | ||
``` | ||
|
||
## `->mapResult()` | ||
|
||
Same as [`->map()`](#map) except that it will transform the _result_ value when there is one. | ||
|
||
## `->mapFailure()` | ||
|
||
Same as [`->map()`](#map) except that it will transform the _failure_ value when there is one. | ||
|
||
## `->maybe()` | ||
|
||
This will return the _terminal_ value of the folding, meaning either a _result_ or a _failure_. | ||
|
||
```php | ||
Fold::with([])->maybe()->match( | ||
static fn() => null, // not called as still folding | ||
static fn() => doStuff(), // called as it is still folding | ||
); | ||
Fold::result([])->maybe()->match( | ||
static fn($either) => $either->match( | ||
static fn($result) => $result, // the value here is the array passed to ::result() above | ||
static fn() => null, // not called as it doesn't contain a failure | ||
), | ||
static fn() => null, // not called as we have a result | ||
); | ||
Fold::fail('some error')->maybe()->match( | ||
static fn($either) => $either->match( | ||
static fn() => null, // not called as we have a failure | ||
static fn($error) => var_dump($error), // the value here is the string passed to ::fail() above | ||
), | ||
static fn() => null, // not called as we have a result | ||
); | ||
``` | ||
|
||
## `->match()` | ||
|
||
This method allows to extract the value contained in the object. | ||
|
||
```php | ||
Fold::with([])->match( | ||
static fn($folding) => doStuf($folding), // value from ::with() | ||
static fn() => null, // not called | ||
static fn() => null, // not called | ||
); | ||
Fold::result([])->match( | ||
static fn() => null, // not called | ||
static fn($result) => doStuf($result), // value from ::result() | ||
static fn() => null, // not called | ||
); | ||
Fold::fail('some error')->match( | ||
static fn() => null, // not called | ||
static fn() => null, // not called | ||
static fn($error) => doStuf($error), // value from ::fail() | ||
); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
<?php | ||
declare(strict_types = 1); | ||
|
||
namespace Innmind\Immutable; | ||
|
||
use Innmind\Immutable\Fold\{ | ||
Implementation, | ||
With, | ||
Result, | ||
Failure, | ||
}; | ||
|
||
/** | ||
* @template F Failure | ||
* @template R Result | ||
* @template C Computation | ||
* @psalm-immutable | ||
*/ | ||
final class Fold | ||
{ | ||
private Implementation $fold; | ||
|
||
private function __construct(Implementation $fold) | ||
{ | ||
$this->fold = $fold; | ||
} | ||
|
||
/** | ||
* @psalm-pure | ||
* | ||
* @template T | ||
* @template U | ||
* @template V | ||
* | ||
* @param V $value | ||
* | ||
* @return self<T, U, V> | ||
*/ | ||
public static function with(mixed $value): self | ||
{ | ||
return new self(new With($value)); | ||
} | ||
|
||
/** | ||
* @psalm-pure | ||
* | ||
* @template T | ||
* @template U | ||
* @template V | ||
* | ||
* @param U $result | ||
* | ||
* @return self<T, U, V> | ||
*/ | ||
public static function result(mixed $result): self | ||
{ | ||
return new self(new Result($result)); | ||
} | ||
|
||
/** | ||
* @psalm-pure | ||
* | ||
* @template T | ||
* @template U | ||
* @template V | ||
* | ||
* @param T $failure | ||
* | ||
* @return self<T, U, V> | ||
*/ | ||
public static function fail(mixed $failure): self | ||
{ | ||
return new self(new Failure($failure)); | ||
} | ||
|
||
/** | ||
* @template A | ||
* | ||
* @param callable(C): A $map | ||
* | ||
* @return self<F, R, A> | ||
*/ | ||
public function map(callable $map): self | ||
{ | ||
return new self($this->fold->map($map)); | ||
} | ||
|
||
/** | ||
* @template T | ||
* @template U | ||
* @template V | ||
* | ||
* @param callable(C): self<T, U, V> $map | ||
* | ||
* @return self<F|T, R|U, V> | ||
*/ | ||
public function flatMap(callable $map): self | ||
{ | ||
return $this->fold->flatMap($map); | ||
} | ||
|
||
/** | ||
* @template A | ||
* | ||
* @param callable(R): A $map | ||
* | ||
* @return self<F, A, C> | ||
*/ | ||
public function mapResult(callable $map): self | ||
{ | ||
return new self($this->fold->mapResult($map)); | ||
} | ||
|
||
/** | ||
* @template A | ||
* | ||
* @param callable(F): A $map | ||
* | ||
* @return self<A, R, C> | ||
*/ | ||
public function mapFailure(callable $map): self | ||
{ | ||
return new self($this->fold->mapFailure($map)); | ||
} | ||
|
||
/** | ||
* @return Maybe<Either<F, R>> | ||
*/ | ||
public function maybe(): Maybe | ||
{ | ||
return $this->fold->maybe(); | ||
} | ||
|
||
/** | ||
* @template T | ||
* | ||
* @param callable(C): T $with | ||
* @param callable(R): T $result | ||
* @param callable(F): T $failure | ||
* | ||
* @return T | ||
*/ | ||
public function match( | ||
callable $with, | ||
callable $result, | ||
callable $failure, | ||
): mixed { | ||
return $this->fold->match($with, $result, $failure); | ||
} | ||
} |
Oops, something went wrong.