diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0c48d373..bfa5286c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,5 +14,3 @@ jobs: uses: innmind/github-workflows/.github/workflows/psalm-matrix.yml@main cs: uses: innmind/github-workflows/.github/workflows/cs.yml@main - with: - php-version: '8.2' diff --git a/.github/workflows/extensive.yml b/.github/workflows/extensive.yml new file mode 100644 index 00000000..257f139e --- /dev/null +++ b/.github/workflows/extensive.yml @@ -0,0 +1,12 @@ +name: Extensive CI + +on: + push: + tags: + - '*' + paths: + - '.github/workflows/extensive.yml' + +jobs: + blackbox: + uses: innmind/github-workflows/.github/workflows/extensive.yml@main diff --git a/CHANGELOG.md b/CHANGELOG.md index 306b7c4d..06c5414e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog +## [Unreleased] + +### Added + +- `Innmind\Immutable\Predicate::and()` +- `Innmind\Immutable\Predicate::or()` +- `Innmind\Immutable\Sequence->lookup()->first()->maybe()` +- `Innmind\Immutable\Sequence->lookup()->first()->attempt()` +- `Innmind\Immutable\Sequence->lookup()->first()->either()` +- `Innmind\Immutable\Sequence->lookup()->last()->maybe()` +- `Innmind\Immutable\Sequence->lookup()->last()->attempt()` +- `Innmind\Immutable\Sequence->lookup()->last()->either()` + +### Changed + +- `Innmind\Immutable\Map::partition()` now returns an array to allow for destructuring +- `Innmind\Immutable\Sequence::partition()` now returns an array to allow for destructuring +- `Innmind\Immutable\Set::partition()` now returns an array to allow for destructuring +- `Innmind\Immutable\Map` no longer implements `Countable` +- `Innmind\Immutable\Sequence` no longer implements `Countable` +- `Innmind\Immutable\Set` no longer implements `Countable` +- Requires PHP `8.4` +- `Innmind\Immutable\Predicate` is now a final class + +### Removed + +- `Innmind\Immutable\Fold` +- `Innmind\Immutable\State` +- `Innmind\Immutable\Sequence::indexOf()` +- `Innmind\Immutable\Set::defer()` +- `Innmind\Immutable\SideEffect::__construct()` +- `Innmind\Immutable\Sequence::count()` +- `Innmind\Immutable\Set::count()` +- `Innmind\Immutable\Map::count()` + +### Fixed + +- PHP `8.5` deprecation + ## 5.20.0 - 2025-09-06 ### Added diff --git a/composer.json b/composer.json index 3420f403..8b53c523 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ "issues": "http://github.com/Innmind/Immutable/issues" }, "require": { - "php": "~8.2" + "php": "~8.4" }, "autoload": { "psr-4": { @@ -30,7 +30,7 @@ } }, "require-dev": { - "innmind/static-analysis": "^1.2.1", + "innmind/static-analysis": "~1.3", "innmind/black-box": "^6.4.1", "innmind/coding-standard": "~2.0" }, diff --git a/docs/structures/fold.md b/docs/structures/fold.md deleted file mode 100644 index 0edb6a4c..00000000 --- a/docs/structures/fold.md +++ /dev/null @@ -1,130 +0,0 @@ -# `Fold` - -??? warning "Deprecated" - `Fold` is deprecated and will be removed in the next major release. - -The `Fold` monad is intented to work with _(infinite) 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, list> */ -$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() -); -``` diff --git a/docs/structures/index.md b/docs/structures/index.md index 848ed5a8..67eb5500 100644 --- a/docs/structures/index.md +++ b/docs/structures/index.md @@ -12,8 +12,6 @@ This library provides the following structures: - [`Attempt`](attempt.md) - [`Validation`](validation.md) - [`Identity`](identity.md) -- [`State`](state.md) -- [`Fold`](fold.md) See the documentation for each structure to understand how to use them. diff --git a/docs/structures/map.md b/docs/structures/map.md index 79c9b040..e8046582 100644 --- a/docs/structures/map.md +++ b/docs/structures/map.md @@ -60,16 +60,6 @@ $map = Map::of([1, 2]); $map->size(); // 1 ``` -### `->count()` - -This is an alias for `->size()`, but you can also use the PHP function `\count` if you prefer. - -```php -$map = Map::of([1, 2]); -$map->size(); // 1 -\count($map); // 1 -``` - ### `->get()` Return an instance of [`Maybe`](maybe.md) that may contain the value associated to the given key (if it exists). @@ -319,26 +309,14 @@ $map ### `->partition()` -This method is similar to `->groupBy()` method but the map keys are always booleans. The difference is that here the 2 keys are always present whereas with `->groupBy()` it will depend on the original map. +This method is similar to `->groupBy()` except it always return 2 `Map`s. The first one contains elements that match the predicate and the second the ones that don't. ```php $map = Map::of([1, 2], [2, 3], [3, 3]); -/** @var Map> */ -$map = $map->partition(fn($key, $value) => ($key + $value) % 2 === 0); -$map - ->get(true) - ->match( - static fn($partition) => $partition, - static fn() => Map::of(), - ) - ->equals(Map::of([3, 3])); // true -$map - ->get(false) - ->match( - static fn($partition) => $partition, - static fn() => Map::of(), - ) - ->equals(Map::of([1, 2], [2, 3])); // true +/** @var array{Map, Map} */ +[$true, $false] = $map->partition(fn($key, $value) => ($key + $value) % 2 === 0); +$true->equals(Map::of([3, 3])); // true +$false->equals(Map::of([1, 2], [2, 3])); // true ``` ### `->clear()` diff --git a/docs/structures/sequence.md b/docs/structures/sequence.md index 83eeee70..8b583d02 100644 --- a/docs/structures/sequence.md +++ b/docs/structures/sequence.md @@ -124,16 +124,6 @@ $sequence = Sequence::ints(1, 4, 6); $sequence->size(); // 3 ``` -### `->count()` :material-memory-arrow-down: - -This is an alias for `->size()`, but you can also use the PHP function `\count` if you prefer. - -```php -$sequence = Sequence::ints(1, 4, 6); -$sequence->count(); // 3 -\count($sequence); // 3 -``` - ### `->get()` This method will return a [`Maybe`](maybe.md) object containing the element at the given index in the sequence. If the index doesn't exist it will an empty `Maybe` object. @@ -163,19 +153,6 @@ $sequence->contains(42); // true $sequence->contains('42'); // false but psalm will raise an error ``` -### `->indexOf()` - -This will return a [`Maybe`](maybe.md) object containing the index number at which the first occurence of the element was found. - -```php -$sequence = Sequence::ints(1, 2, 3, 2); -$sequence->indexOf(2); // Maybe::just(1) -$sequence->indexOf(4); // Maybe::nothing() -``` - -??? warning "Deprecated" - This method will be remove in the next major version. - ### `->find()` Returns a [`Maybe`](maybe.md) object containing the first element that matches the predicate. @@ -187,6 +164,78 @@ $firstOdd; // Maybe::just(9) $sequence->find(static fn() => false); // Maybe::nothing() ``` +### `->lookup()` + +This is similar to `->find()` except it allows to return a computed value while searching for it. This is useful when parsing content. + +=== "`->maybe()`" + ```php + final class Email + { + /** + * @return Maybe + */ + public static function of(string $email): Maybe + { + } + } + + $email = Sequence::of('foo', 'valid-email@example.com', 'invalid') + ->lookup() + ->first() + ->maybe(Email::of(...)); + $email == Maybe::just(new Email('valid-email@example.com')); // true + ``` + +=== "`->attempt()`" + ```php + final class Email + { + /** + * @return Attempt + */ + public static function of(string $email): Attempt + { + } + } + + $email = Sequence::of('foo', 'valid-email@example.com', 'invalid') + ->lookup() + ->first() + ->attempt( + new \RuntimeException('No valid email found'), + Email::of(...), + ); + $email == Attempt::result(new Email('valid-email@example.com')); // true + ``` + +=== "`->either()`" + ```php + final class Email + { + /** + * @return Either + */ + public static function of(string $email): Either + { + } + } + + $email = Sequence::of('foo', 'valid-email@example.com', 'invalid') + ->lookup() + ->first() + ->attempt( + 'No valid email found', #(1) + Email::of(...), + ); + $email == Either::right(new Email('valid-email@example.com')); // true + ``` + + 1. Must be of the same type as the left side returned by `Email::of()` + +!!! note "" + If you need to access the last value that matches the predicate you can use `#!php ->lookup()->last()` instead of `#!php ->lookup()->first()`. + ### `->matches()` :material-memory-arrow-down: Check if all the elements of the sequence matches the given predicate. @@ -739,26 +788,14 @@ $sequence->pad(5, 0)->equals(Sequence::ints(1, 2, 3, 0, 0)); // true ### `->partition()` :material-memory-arrow-down: -This method is similar to `->groupBy()` method but the map keys are always booleans. The difference is that here the 2 keys are always present whereas with `->groupBy()` it will depend on the original sequence. +This method is similar to `->groupBy()` except it always return 2 `Sequence`s. The first one contains elements that match the predicate and the second the ones that don't. ```php $sequence = Sequence::ints(1, 2, 3); -/** @var Map> */ -$map = $sequence->partition(fn($int) => $int % 2 === 0); -$map - ->get(true) - ->match( - static fn($partition) => $partition, - static fn() => Sequence::ints(), - ) - ->equals(Sequence::ints(2)); // true -$map - ->get(false) - ->match( - static fn($partition) => $partition, - static fn() => Sequence::ints(), - ) - ->equals(Sequence::ints(1, 3)); // true +/** @var array{Sequence, Sequence} */ +[$true, $false] = $sequence->partition(fn($int) => $int % 2 === 0); +$true->equals(Sequence::ints(2)); // true +$false->equals(Sequence::ints(1, 3)); // true ``` ### `->sort()` diff --git a/docs/structures/set.md b/docs/structures/set.md index 449b77b5..22d722f3 100644 --- a/docs/structures/set.md +++ b/docs/structures/set.md @@ -13,26 +13,6 @@ use Innmind\Immutable\Set; Set::of(1, 2, 3, $etc); ``` -### `::defer()` - -This named constructor is for advanced use cases where you want the data of your set to be loaded upon use only and not initialisation. - -An example for such a use case is a set of log lines coming from a file: - -```php -$set = Set::defer((function() { - yield from readSomeFile('apache.log'); -})()); -``` - -The method ask a generator that will provide the elements. Once the elements are loaded they are kept in memory so you can run multiple operations on it without loading the file twice. - -!!! warning "" - Beware of the case where the source you read the elements is not altered before the first use of the set. - -??? warning "Deprecated" - This constructor is deprecated. You should use `Set::lazy()->snap()` instead. - ### `::lazy()` This is similar to `::defer()` with the exception that the elements are not kept in memory but reloaded upon each use. @@ -102,16 +82,6 @@ $set = Set::ints(1, 4, 6); $set->size(); // 3 ``` -### `->count()` - -This is an alias for `->size()`, but you can also use the PHP function `\count` if you prefer. - -```php -$set = Set::ints(1, 4, 6); -$set->count(); // 3 -\count($set); // 3 -``` - ### `->contains()` Check if the element is present in the set. @@ -341,26 +311,14 @@ $map ### `->partition()` -This method is similar to `->groupBy()` method but the map keys are always booleans. The difference is that here the 2 keys are always present whereas with `->groupBy()` it will depend on the original set. +This method is similar to `->groupBy()` except it always return 2 `Set`s. The first one contains elements that match the predicate and the second the ones that don't. ```php $set = Set::ints(1, 2, 3); -/** @var Map> */ -$map = $set->partition(fn($int) => $int % 2 === 0); -$map - ->get(true) - ->match( - static fn($partition) => $partition, - static fn() => Set::ints(), - ) - ->equals(Set::ints(2)); // true -$map - ->get(false) - ->match( - static fn($partition) => $partition, - static fn() => Set::ints(), - ) - ->equals(Set::ints(1, 3)); // true +/** @var array{Set, Set} */ +[$true, $false] = $set->partition(fn($int) => $int % 2 === 0); +$true->equals(Set::ints(2)); // true +$false->equals(Set::ints(1, 3)); // true ``` ### `->sort()` diff --git a/docs/structures/state.md b/docs/structures/state.md deleted file mode 100644 index 811fdf50..00000000 --- a/docs/structures/state.md +++ /dev/null @@ -1,94 +0,0 @@ -# `State` - -??? warning "Deprecated" - `State` is deprecated and will be removed in the next major release. - -The `State` monad allows you to build a set of pure steps to compute a new state. Since the initial state is given when all the steps are built it means that all steps are lazy, this use function composition (so everything is kept in memory). - -The state and value can be of any type. - -## `::of()` - -```php -use Innmind\Immutable\{ - State, - State\Result, -}; - -/** @var State */ -$state = State::of(function(array $logs) { - return Result::of($logs, 0); -}); -``` - -## `->map()` - -This method will modify the value without affecting the currently held state. - -```php -use Innmind\Immutable\{ - State, - State\Result, -}; - -/** @var State */ -$state = State::of(function(array $logs) { - return Result::of($logs, 0); -}); - -$state = $state->map(fn($value) => $value + 1); -``` - -## `->flatMap()` - -This method allows you to modify both state and values. - -```php -use Innmind\Immutable\{ - State, - State\Result, -}; - -/** @var State */ -$state = State::of(function(array $logs) { - return Result::of($logs, 0); -}); - -$state = $state->flatMap(fn($value) => State::of(function(array $logs) use ($value) { - $value++; - - return Result::of( - \array_merge($logs, "The new value is $value"), - $value, - ); -})); -``` - -## `->run()` - -This is the only place where you can run the steps to compute the new state. - -```php -use Innmind\Immutable\{ - State, - State\Result, -}; - -/** @var State */ -$result = State::of(function(array $logs) { - return Result::of($logs, 0); -}) - ->map(fn($value) => $value + 1) - ->flatMap(fn($value) => State::of(function(array $logs) use ($value) { - $value++; - - return Result::of( - \array_merge($logs, "The new value is $value"), - $value, - ); - })) - ->run([]); - -$result->state(); // ['The new value is 2'] -$result->value(); // 2 -``` diff --git a/mkdocs.yml b/mkdocs.yml index 43539353..f3f4a2e0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -16,8 +16,6 @@ nav: - Attempt: structures/attempt.md - Validation: structures/validation.md - Identity: structures/identity.md - - State: structures/state.md - - Fold: structures/fold.md - Monoids: MONOIDS.md - Use cases: - use-cases/index.md diff --git a/proofs/attempt.php b/proofs/attempt.php index 16dcdcdb..de254391 100644 --- a/proofs/attempt.php +++ b/proofs/attempt.php @@ -301,7 +301,7 @@ static function($assert, $result, $e) { static fn() => null, ), ); - $attempt->memoize(); + $_ = $attempt->memoize(); $assert->same(1, $called); $called = 0; @@ -321,7 +321,7 @@ static function($assert, $result, $e) { static fn($value) => $value, ), ); - $attempt->memoize(); + $_ = $attempt->memoize(); $assert->same(1, $called); }, ); @@ -346,12 +346,12 @@ static function($assert, $result1, $result2, $e1, $e2) { ->recover(static fn() => Attempt::error($e2)); $assert->false($loaded); - $attempt->maybe(); + $_ = $attempt->maybe(); $assert->false($loaded); - $attempt->either(); + $_ = $attempt->either(); $assert->false($loaded); - $attempt->memoize(); + $_ = $attempt->memoize(); $assert->true($loaded); $assert->same( diff --git a/proofs/either.php b/proofs/either.php index d5f83bb1..745de283 100644 --- a/proofs/either.php +++ b/proofs/either.php @@ -20,7 +20,7 @@ static function($assert, $value) { }); $assert->false($loaded); - $either->memoize(); + $_ = $either->memoize(); $assert->true($loaded); }, ); diff --git a/proofs/maybe.php b/proofs/maybe.php index f530f079..7717b967 100644 --- a/proofs/maybe.php +++ b/proofs/maybe.php @@ -59,7 +59,7 @@ static function($assert, $value) { }); $assert->false($loaded); - $maybe->memoize(); + $_ = $maybe->memoize(); $assert->true($loaded); }, ); diff --git a/proofs/sequence.php b/proofs/sequence.php index 6b90b78b..198aa448 100644 --- a/proofs/sequence.php +++ b/proofs/sequence.php @@ -78,12 +78,12 @@ static function($assert, $string, $chunk) { ->split() ->chunk($chunk); - $chunks->foreach( + $_ = $chunks->foreach( static fn($chars) => $assert ->number($chars->size()) ->lessThanOrEqual($chunk), ); - $chunks + $_ = $chunks ->dropEnd(1) ->foreach( static fn($chars) => $assert->same( @@ -160,7 +160,7 @@ static function($assert, $calls) { } try { - $sequence->toList(); + $_ = $sequence->toList(); $assert->fail('it should throw'); } catch (Exception $e) { $assert->same($expected, $e); diff --git a/proofs/set.php b/proofs/set.php index c85a59c8..a60743ea 100644 --- a/proofs/set.php +++ b/proofs/set.php @@ -56,11 +56,11 @@ static function($assert, $values) { yield test( 'Set defer nesting calls', static function($assert) { - $set = Set::defer((static function() { + $set = Set::lazy(static function() { yield 1; yield 2; yield 3; - })()); + })->snap(); $assert->same( [ @@ -154,11 +154,11 @@ static function($assert) { yield test( 'Set defer partial nesting calls', static function($assert) { - $set = Set::defer((static function() { + $set = Set::lazy(static function() { yield 1; yield 2; yield 3; - })()); + })->snap(); $assert->same( [ @@ -216,43 +216,6 @@ static function($assert, $value, $rest) { }, ); - yield proof( - 'Set::snap() loads a deferred set at once', - given( - DataSet::type(), - DataSet::sequence(DataSet::type()), - )->filter(static fn($value, $rest) => !\in_array($value, $rest, true)), - static function($assert, $value, $rest) { - $loaded = 0; - $sequence = Set::defer((static function() use (&$loaded, $value, $rest) { - yield $value; - yield from $rest; - - ++$loaded; - })()) - ->snap() - ->map(static fn($value) => [$value]); - - $assert->same(0, $loaded); - $assert->same( - [$value], - $sequence->find(static fn() => true)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - $assert->same(1, $loaded); - $assert->same( - [$value], - $sequence->find(static fn() => true)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - $assert->same(1, $loaded); - }, - ); - yield proof( 'Set::equals() should not load its data when compared to itself', given(DataSet::sequence(DataSet::type())), diff --git a/properties/Sequence.php b/properties/Sequence.php index fa9ef5f5..22b2ff13 100644 --- a/properties/Sequence.php +++ b/properties/Sequence.php @@ -34,6 +34,12 @@ public static function list(): array { return [ Sequence\Windows::class, + Sequence\LookupFirstMaybe::class, + Sequence\LookupFirstAttempt::class, + Sequence\LookupFirstEither::class, + Sequence\LookupLastMaybe::class, + Sequence\LookupLastAttempt::class, + Sequence\LookupLastEither::class, ]; } } diff --git a/properties/Sequence/LookupFirstAttempt.php b/properties/Sequence/LookupFirstAttempt.php new file mode 100644 index 00000000..fc6fc110 --- /dev/null +++ b/properties/Sequence/LookupFirstAttempt.php @@ -0,0 +1,82 @@ + + */ +final class LookupFirstAttempt implements Property +{ + private function __construct( + private Sequence $prefix, + private mixed $a, + private mixed $b, + ) { + } + + public static function any(): Set\Provider + { + return Set::compose( + static fn(...$args) => new self(...$args), + Set::sequence(Set::type())->map(static fn($values) => Sequence::of(...$values)), + Set::type(), + Set::type(), + ); + } + + public function applicableTo(object $systemUnderTest): bool + { + return true; + } + + public function ensureHeldBy(Assert $assert, object $systemUnderTest): object + { + $default = new \Exception; + + $assert->same( + $default, + $systemUnderTest + ->lookup() + ->first() + ->attempt( + $default, + static fn() => Attempt::error($default), + ) + ->match( + static fn() => null, + static fn($error) => $error, + ), + ); + $assert->same( + $this->b, + $systemUnderTest + ->prepend($this->prefix->add($this->a)) + ->lookup() + ->first() + ->attempt( + $default, + fn($value) => match ($value) { + $this->a => Attempt::result($this->b), + default => Attempt::error($default), + }, + ) + ->match( + static fn($value) => $value, + static fn() => null, + ), + ); + + return $systemUnderTest; + } +} diff --git a/properties/Sequence/LookupFirstEither.php b/properties/Sequence/LookupFirstEither.php new file mode 100644 index 00000000..6347e3cd --- /dev/null +++ b/properties/Sequence/LookupFirstEither.php @@ -0,0 +1,82 @@ + + */ +final class LookupFirstEither implements Property +{ + private function __construct( + private Sequence $prefix, + private mixed $a, + private mixed $b, + private mixed $left, + ) { + } + + public static function any(): Set\Provider + { + return Set::compose( + static fn(...$args) => new self(...$args), + Set::sequence(Set::type())->map(static fn($values) => Sequence::of(...$values)), + Set::type(), + Set::type(), + Set::type(), + ); + } + + public function applicableTo(object $systemUnderTest): bool + { + return true; + } + + public function ensureHeldBy(Assert $assert, object $systemUnderTest): object + { + $assert->same( + $this->left, + $systemUnderTest + ->lookup() + ->first() + ->either( + $this->left, + fn() => Either::left($this->left), + ) + ->match( + static fn() => null, + static fn($left) => $left, + ), + ); + $assert->same( + $this->b, + $systemUnderTest + ->prepend($this->prefix->add($this->a)) + ->lookup() + ->first() + ->either( + $this->left, + fn($value) => match ($value) { + $this->a => Either::right($this->b), + default => Either::left($this->left), + }, + ) + ->match( + static fn($value) => $value, + static fn() => null, + ), + ); + + return $systemUnderTest; + } +} diff --git a/properties/Sequence/LookupFirstMaybe.php b/properties/Sequence/LookupFirstMaybe.php new file mode 100644 index 00000000..a535b940 --- /dev/null +++ b/properties/Sequence/LookupFirstMaybe.php @@ -0,0 +1,73 @@ + + */ +final class LookupFirstMaybe implements Property +{ + private function __construct( + private Sequence $prefix, + private mixed $a, + private mixed $b, + ) { + } + + public static function any(): Set\Provider + { + return Set::compose( + static fn(...$args) => new self(...$args), + Set::sequence(Set::type())->map(static fn($values) => Sequence::of(...$values)), + Set::type(), + Set::type(), + ); + } + + public function applicableTo(object $systemUnderTest): bool + { + return true; + } + + public function ensureHeldBy(Assert $assert, object $systemUnderTest): object + { + $assert->null( + $systemUnderTest + ->lookup() + ->first() + ->maybe(Maybe::nothing(...)) + ->match( + static fn($value) => $value, + static fn() => null, + ), + ); + $assert->same( + $this->b, + $systemUnderTest + ->prepend($this->prefix->add($this->a)) + ->lookup() + ->first() + ->maybe(fn($value) => match ($value) { + $this->a => Maybe::just($this->b), + default => Maybe::nothing(), + }) + ->match( + static fn($value) => $value, + static fn() => null, + ), + ); + + return $systemUnderTest; + } +} diff --git a/properties/Sequence/LookupLastAttempt.php b/properties/Sequence/LookupLastAttempt.php new file mode 100644 index 00000000..fb7ee477 --- /dev/null +++ b/properties/Sequence/LookupLastAttempt.php @@ -0,0 +1,85 @@ + + */ +final class LookupLastAttempt implements Property +{ + private function __construct( + private Sequence $suffix, + private mixed $a, + private mixed $b, + ) { + } + + public static function any(): Set\Provider + { + return Set::compose( + static fn(...$args) => new self(...$args), + Set::sequence(Set::type())->map(static fn($values) => Sequence::of(...$values)), + Set::type(), + Set::type(), + ); + } + + public function applicableTo(object $systemUnderTest): bool + { + return true; + } + + public function ensureHeldBy(Assert $assert, object $systemUnderTest): object + { + $default = new \Exception; + + $assert->same( + $default, + $systemUnderTest + ->lookup() + ->last() + ->attempt( + $default, + static fn() => Attempt::error($default), + ) + ->match( + static fn() => null, + static fn($error) => $error, + ), + ); + $assert->same( + $this->b, + $systemUnderTest + ->add($this->a) + ->append($this->suffix->exclude( + fn($value) => $value === $this->a, + )) + ->lookup() + ->last() + ->attempt( + $default, + fn($value) => match ($value) { + $this->a => Attempt::result($this->b), + default => Attempt::error($default), + }, + ) + ->match( + static fn($value) => $value, + static fn() => null, + ), + ); + + return $systemUnderTest; + } +} diff --git a/properties/Sequence/LookupLastEither.php b/properties/Sequence/LookupLastEither.php new file mode 100644 index 00000000..5f4f61cd --- /dev/null +++ b/properties/Sequence/LookupLastEither.php @@ -0,0 +1,85 @@ + + */ +final class LookupLastEither implements Property +{ + private function __construct( + private Sequence $suffix, + private mixed $a, + private mixed $b, + private mixed $left, + ) { + } + + public static function any(): Set\Provider + { + return Set::compose( + static fn(...$args) => new self(...$args), + Set::sequence(Set::type())->map(static fn($values) => Sequence::of(...$values)), + Set::type(), + Set::type(), + Set::type(), + ); + } + + public function applicableTo(object $systemUnderTest): bool + { + return true; + } + + public function ensureHeldBy(Assert $assert, object $systemUnderTest): object + { + $assert->same( + $this->left, + $systemUnderTest + ->lookup() + ->last() + ->either( + $this->left, + fn() => Either::left($this->left), + ) + ->match( + static fn() => null, + static fn($left) => $left, + ), + ); + $assert->same( + $this->b, + $systemUnderTest + ->add($this->a) + ->append($this->suffix->exclude( + fn($value) => $value === $this->a, + )) + ->lookup() + ->last() + ->either( + $this->left, + fn($value) => match ($value) { + $this->a => Either::right($this->b), + default => Either::left($this->left), + }, + ) + ->match( + static fn($value) => $value, + static fn() => null, + ), + ); + + return $systemUnderTest; + } +} diff --git a/properties/Sequence/LookupLastMaybe.php b/properties/Sequence/LookupLastMaybe.php new file mode 100644 index 00000000..81fa5287 --- /dev/null +++ b/properties/Sequence/LookupLastMaybe.php @@ -0,0 +1,76 @@ + + */ +final class LookupLastMaybe implements Property +{ + private function __construct( + private Sequence $suffix, + private mixed $a, + private mixed $b, + ) { + } + + public static function any(): Set\Provider + { + return Set::compose( + static fn(...$args) => new self(...$args), + Set::sequence(Set::type())->map(static fn($values) => Sequence::of(...$values)), + Set::type(), + Set::type(), + ); + } + + public function applicableTo(object $systemUnderTest): bool + { + return true; + } + + public function ensureHeldBy(Assert $assert, object $systemUnderTest): object + { + $assert->null( + $systemUnderTest + ->lookup() + ->last() + ->maybe(Maybe::nothing(...)) + ->match( + static fn($value) => $value, + static fn() => null, + ), + ); + $assert->same( + $this->b, + $systemUnderTest + ->add($this->a) + ->append($this->suffix->exclude( + fn($value) => $value === $this->a, + )) + ->lookup() + ->last() + ->maybe(fn($value) => match ($value) { + $this->a => Maybe::just($this->b), + default => Maybe::nothing(), + }) + ->match( + static fn($value) => $value, + static fn() => null, + ), + ); + + return $systemUnderTest; + } +} diff --git a/properties/Sequence/Windows.php b/properties/Sequence/Windows.php index 9a7cdca1..6aaa5403 100644 --- a/properties/Sequence/Windows.php +++ b/properties/Sequence/Windows.php @@ -36,7 +36,7 @@ public function applicableTo(object $systemUnderTest): bool public function ensureHeldBy(Assert $assert, object $systemUnderTest): object { - $systemUnderTest + $_ = $systemUnderTest ->windows($this->size) ->foreach( fn($window) => $assert @@ -46,7 +46,7 @@ public function ensureHeldBy(Assert $assert, object $systemUnderTest): object ); if ($systemUnderTest->size() >= $this->size) { - $systemUnderTest + $_ = $systemUnderTest ->windows($this->size) ->foreach(fn($window) => $assert->same( $this->size, diff --git a/src/Fold.php b/src/Fold.php deleted file mode 100644 index 0e7820e0..00000000 --- a/src/Fold.php +++ /dev/null @@ -1,158 +0,0 @@ - - */ - #[\NoDiscard] - 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 - */ - #[\NoDiscard] - 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 - */ - #[\NoDiscard] - public static function fail(mixed $failure): self - { - return new self(new Failure($failure)); - } - - /** - * @template A - * - * @param callable(C): A $map - * - * @return self - */ - #[\NoDiscard] - public function map(callable $map): self - { - return new self($this->fold->map($map)); - } - - /** - * @template T - * @template U - * @template V - * - * @param callable(C): self $map - * - * @return self - */ - #[\NoDiscard] - public function flatMap(callable $map): self - { - return $this->fold->flatMap($map); - } - - /** - * @template A - * - * @param callable(R): A $map - * - * @return self - */ - #[\NoDiscard] - public function mapResult(callable $map): self - { - return new self($this->fold->mapResult($map)); - } - - /** - * @template A - * - * @param callable(F): A $map - * - * @return self - */ - #[\NoDiscard] - public function mapFailure(callable $map): self - { - return new self($this->fold->mapFailure($map)); - } - - /** - * @return Maybe> - */ - #[\NoDiscard] - 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 - */ - #[\NoDiscard] - public function match( - callable $with, - callable $result, - callable $failure, - ): mixed { - return $this->fold->match($with, $result, $failure); - } -} diff --git a/src/Fold/Failure.php b/src/Fold/Failure.php deleted file mode 100644 index b095d184..00000000 --- a/src/Fold/Failure.php +++ /dev/null @@ -1,91 +0,0 @@ - - * @psalm-immutable - * @internal - * @psalm-suppress DeprecatedClass - */ -final class Failure implements Implementation -{ - /** - * @param F1 $failure - */ - public function __construct( - private mixed $failure, - ) { - } - - /** - * @template A - * - * @param callable(C1): A $map - * - * @return self - */ - #[\Override] - public function map(callable $map): self - { - /** @var self */ - return $this; - } - - #[\Override] - public function flatMap(callable $map): Fold - { - return Fold::fail($this->failure); - } - - /** - * @template A - * - * @param callable(R1): A $map - * - * @return self - */ - #[\Override] - public function mapResult(callable $map): self - { - /** @var self */ - return $this; - } - - #[\Override] - public function mapFailure(callable $map): self - { - /** @psalm-suppress ImpureFunctionCall */ - return new self($map($this->failure)); - } - - /** - * @return Maybe> - */ - #[\Override] - public function maybe(): Maybe - { - /** @var Maybe> */ - return Maybe::just(Either::left($this->failure)); - } - - #[\Override] - public function match( - callable $with, - callable $result, - callable $failure, - ): mixed { - /** @psalm-suppress ImpureFunctionCall */ - return $failure($this->failure); - } -} diff --git a/src/Fold/Implementation.php b/src/Fold/Implementation.php deleted file mode 100644 index a56f260f..00000000 --- a/src/Fold/Implementation.php +++ /dev/null @@ -1,79 +0,0 @@ - - */ - public function map(callable $map): self; - - /** - * @template T - * @template U - * @template V - * - * @param callable(C): Fold $map - * - * @return Fold - */ - public function flatMap(callable $map): Fold; - - /** - * @template A - * - * @param callable(R): A $map - * - * @return self - */ - public function mapResult(callable $map): self; - - /** - * @template A - * - * @param callable(F): A $map - * - * @return self - */ - public function mapFailure(callable $map): self; - - /** - * @return Maybe> - */ - public function maybe(): 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; -} diff --git a/src/Fold/Result.php b/src/Fold/Result.php deleted file mode 100644 index 3d9b3f7f..00000000 --- a/src/Fold/Result.php +++ /dev/null @@ -1,91 +0,0 @@ - - * @psalm-immutable - * @internal - * @psalm-suppress DeprecatedClass - */ -final class Result implements Implementation -{ - /** - * @param R1 $result - */ - public function __construct( - private mixed $result, - ) { - } - - /** - * @template A - * - * @param callable(C1): A $map - * - * @return self - */ - #[\Override] - public function map(callable $map): self - { - /** @var self */ - return $this; - } - - #[\Override] - public function flatMap(callable $map): Fold - { - return Fold::result($this->result); - } - - #[\Override] - public function mapResult(callable $map): self - { - /** @psalm-suppress ImpureFunctionCall */ - return new self($map($this->result)); - } - - /** - * @template A - * - * @param callable(F1): A $map - * - * @return self - */ - #[\Override] - public function mapFailure(callable $map): self - { - /** @var self */ - return $this; - } - - /** - * @return Maybe> - */ - #[\Override] - public function maybe(): Maybe - { - /** @var Maybe> */ - return Maybe::just(Either::right($this->result)); - } - - #[\Override] - public function match( - callable $with, - callable $result, - callable $failure, - ): mixed { - /** @psalm-suppress ImpureFunctionCall */ - return $result($this->result); - } -} diff --git a/src/Fold/With.php b/src/Fold/With.php deleted file mode 100644 index 901353c7..00000000 --- a/src/Fold/With.php +++ /dev/null @@ -1,102 +0,0 @@ - - * @psalm-immutable - * @internal - * @psalm-suppress DeprecatedClass - */ -final class With implements Implementation -{ - /** - * @param C1 $with - */ - public function __construct( - private mixed $with, - ) { - } - - /** - * @template A - * - * @param callable(C1): A $map - * - * @return self - */ - #[\Override] - public function map(callable $map): self - { - /** - * @psalm-suppress ImpureFunctionCall - * @var self - */ - return new self($map($this->with)); - } - - #[\Override] - public function flatMap(callable $map): Fold - { - /** @psalm-suppress ImpureFunctionCall */ - return $map($this->with); - } - - /** - * @template A - * - * @param callable(R1): A $map - * - * @return self - */ - #[\Override] - public function mapResult(callable $map): self - { - /** @var self */ - return $this; - } - - /** - * @template A - * - * @param callable(F1): A $map - * - * @return self - */ - #[\Override] - public function mapFailure(callable $map): self - { - /** @var self */ - return $this; - } - - /** - * @return Maybe> - */ - #[\Override] - public function maybe(): Maybe - { - /** @var Maybe> */ - return Maybe::nothing(); - } - - #[\Override] - public function match( - callable $with, - callable $result, - callable $failure, - ): mixed { - /** @psalm-suppress ImpureFunctionCall */ - return $with($this->with); - } -} diff --git a/src/Map.php b/src/Map.php index a54c2956..3971bfc9 100644 --- a/src/Map.php +++ b/src/Map.php @@ -8,7 +8,7 @@ * @template-covariant S * @psalm-immutable */ -final class Map implements \Countable +final class Map { /** * @param Map\Implementation $implementation @@ -69,16 +69,6 @@ public function size(): int return $this->implementation->size(); } - /** - * @return int<0, max> - */ - #[\Override] - #[\NoDiscard] - public function count(): int - { - return $this->size(); - } - /** * Set a new key/value pair * @@ -283,14 +273,16 @@ public function merge(self $map): self } /** - * Return a map of 2 maps partitioned according to the given predicate + * Return 2 Maps partitioned according to the given predicate. + * + * The first Map contains values that matched the predicate. * * @param callable(T, S): bool $predicate * - * @return self> + * @return array{self, self} */ #[\NoDiscard] - public function partition(callable $predicate): self + public function partition(callable $predicate): array { return $this->implementation->partition($predicate); } diff --git a/src/Map/DoubleIndex.php b/src/Map/DoubleIndex.php index 86a0955f..5dfc2b7e 100644 --- a/src/Map/DoubleIndex.php +++ b/src/Map/DoubleIndex.php @@ -67,12 +67,6 @@ public function size(): int return $this->pairs->size(); } - #[\Override] - public function count(): int - { - return $this->size(); - } - /** * @param T $key * @@ -256,15 +250,15 @@ public function merge(Implementation $map): self /** * @param callable(T, S): bool $predicate * - * @return Map> + * @return array{Map, Map} */ #[\Override] - public function partition(callable $predicate): Map + public function partition(callable $predicate): array { $truthy = $this->clearMap(); $falsy = $this->clearMap(); - [$truthy, $falsy] = $this->pairs->reduce( + return $this->pairs->reduce( [$truthy, $falsy], static function(array $carry, $pair) use ($predicate) { /** @@ -288,8 +282,6 @@ static function(array $carry, $pair) use ($predicate) { return [$truthy, $falsy]; }, ); - - return Map::of([true, $truthy], [false, $falsy]); } /** diff --git a/src/Map/Implementation.php b/src/Map/Implementation.php index f14f5f7f..563b7a46 100644 --- a/src/Map/Implementation.php +++ b/src/Map/Implementation.php @@ -18,7 +18,7 @@ * @internal Dot not code against this interface * @psalm-immutable */ -interface Implementation extends \Countable +interface Implementation { /** * Set a new key/value pair @@ -141,9 +141,9 @@ public function merge(self $map): self; * * @param callable(T, S): bool $predicate * - * @return Map> + * @return array{Map, Map} */ - public function partition(callable $predicate): Map; + public function partition(callable $predicate): array; /** * Reduce the map to a single value diff --git a/src/Map/ObjectKeys.php b/src/Map/ObjectKeys.php index 95f64e7b..bcbfac85 100644 --- a/src/Map/ObjectKeys.php +++ b/src/Map/ObjectKeys.php @@ -82,12 +82,6 @@ public function size(): int return $this->values->count(); } - #[\Override] - public function count(): int - { - return $this->size(); - } - /** * @param T $key * @@ -332,7 +326,7 @@ public function remove($key): self * @psalm-suppress MixedArgumentTypeCoercion * @psalm-suppress ImpureMethodCall */ - $values->detach($key); + $values->offsetUnset($key); /** @psalm-suppress ImpureMethodCall */ $values->rewind(); @@ -356,10 +350,10 @@ public function merge(Implementation $map): Implementation /** * @param callable(T, S): bool $predicate * - * @return Map> + * @return array{Map, Map} */ #[\Override] - public function partition(callable $predicate): Map + public function partition(callable $predicate): array { $truthy = $this->clearMap(); $falsy = $this->clearMap(); @@ -384,7 +378,7 @@ public function partition(callable $predicate): Map } } - return Map::of([true, $truthy], [false, $falsy]); + return [$truthy, $falsy]; } /** diff --git a/src/Map/Primitive.php b/src/Map/Primitive.php index fab0e638..e8ec745a 100644 --- a/src/Map/Primitive.php +++ b/src/Map/Primitive.php @@ -90,12 +90,6 @@ public function size(): int return \count($this->values); } - #[\Override] - public function count(): int - { - return $this->size(); - } - /** * @param T $key * @@ -301,10 +295,10 @@ public function merge(Implementation $map): Implementation /** * @param callable(T, S): bool $predicate * - * @return Map> + * @return array{Map, Map} */ #[\Override] - public function partition(callable $predicate): Map + public function partition(callable $predicate): array { $truthy = $this->clearMap(); $falsy = $this->clearMap(); @@ -320,7 +314,7 @@ public function partition(callable $predicate): Map } } - return Map::of([true, $truthy], [false, $falsy]); + return [$truthy, $falsy]; } /** diff --git a/src/Map/Uninitialized.php b/src/Map/Uninitialized.php index a42a4108..f9a51b2a 100644 --- a/src/Map/Uninitialized.php +++ b/src/Map/Uninitialized.php @@ -58,12 +58,6 @@ public function size(): int return 0; } - #[\Override] - public function count(): int - { - return $this->size(); - } - /** * @param T $key * @@ -194,15 +188,15 @@ public function merge(Implementation $map): Implementation /** * @param callable(T, S): bool $predicate * - * @return Map> + * @return array{Map, Map} */ #[\Override] - public function partition(callable $predicate): Map + public function partition(callable $predicate): array { - return Map::of( - [true, $this->clearMap()], - [false, $this->clearMap()], - ); + return [ + $this->clearMap(), + $this->clearMap(), + ]; } /** diff --git a/src/Predicate.php b/src/Predicate.php index 3266e69c..9c07fb78 100644 --- a/src/Predicate.php +++ b/src/Predicate.php @@ -7,11 +7,68 @@ * @psalm-immutable * @template T */ -interface Predicate +final class Predicate { + /** + * @param \Closure(mixed): bool $assert + */ + private function __construct( + private \Closure $assert, + ) { + } + /** * @psalm-assert-if-true T $value */ #[\NoDiscard] - public function __invoke(mixed $value): bool; + public function __invoke(mixed $value): bool + { + /** @psalm-suppress ImpureFunctionCall */ + return ($this->assert)($value); + } + + /** + * @psalm-pure + * + * @param callable(mixed): bool $assert + */ + public static function of(callable $assert): self + { + return new self(\Closure::fromCallable($assert)); + } + + /** + * @template U + * + * @param self $predicate + * + * @return self + */ + public function and(self $predicate): self + { + $self = $this->assert; + $other = $predicate->assert; + + /** @var self */ + return new self( + static fn($value) => $self($value) && $other($value), + ); + } + + /** + * @template U + * + * @param self $predicate + * + * @return self + */ + public function or(self $predicate): self + { + $self = $this->assert; + $other = $predicate->assert; + + return new self( + static fn($value) => $self($value) || $other($value), + ); + } } diff --git a/src/Predicate/AndPredicate.php b/src/Predicate/AndPredicate.php deleted file mode 100644 index a47c8ded..00000000 --- a/src/Predicate/AndPredicate.php +++ /dev/null @@ -1,85 +0,0 @@ - - */ -final class AndPredicate implements Predicate -{ - /** - * @param Predicate $a - * @param Predicate $b - */ - private function __construct( - private Predicate $a, - private Predicate $b, - ) { - } - - #[\Override] - public function __invoke(mixed $value): bool - { - return ($this->a)($value) && ($this->b)($value); - } - - /** - * @psalm-pure - * @template T - * @template V - * - * @param Predicate $a - * @param Predicate $b - * - * @return self - */ - #[\NoDiscard] - public static function of(Predicate $a, Predicate $b): self - { - return new self($a, $b); - } - - /** - * @template C - * - * @param Predicate $other - * - * @return OrPredicate - */ - #[\NoDiscard] - public function or(Predicate $other): OrPredicate - { - /** - * For some reason if using directly $this below Psalm loses the B type - * @var Predicate - */ - $self = $this; - - return OrPredicate::of($self, $other); - } - - /** - * @template C - * - * @param Predicate $other - * - * @return self - */ - #[\NoDiscard] - public function and(Predicate $other): self - { - /** - * For some reason if using directly $this below Psalm loses the B type - * @var Predicate - */ - $self = $this; - - return new self($self, $other); - } -} diff --git a/src/Predicate/Instance.php b/src/Predicate/Instance.php index f53ee96b..d658d966 100644 --- a/src/Predicate/Instance.php +++ b/src/Predicate/Instance.php @@ -7,62 +7,23 @@ /** * @psalm-immutable - * @template A of object - * @implements Predicate */ -final class Instance implements Predicate +final class Instance { - /** - * @param class-string $class - */ - private function __construct( - private string $class, - ) { - } - - #[\Override] - public function __invoke(mixed $value): bool - { - return $value instanceof $this->class; - } - /** * @psalm-pure * @template T * * @param class-string $class * - * @return self - */ - #[\NoDiscard] - public static function of(string $class): self - { - return new self($class); - } - - /** - * @template T - * - * @param Predicate $predicate - * - * @return OrPredicate - */ - #[\NoDiscard] - public function or(Predicate $predicate): OrPredicate - { - return OrPredicate::of($this, $predicate); - } - - /** - * @template T - * - * @param Predicate $predicate - * - * @return AndPredicate + * @return Predicate */ #[\NoDiscard] - public function and(Predicate $predicate): AndPredicate + public static function of(string $class): Predicate { - return AndPredicate::of($this, $predicate); + /** @var Predicate */ + return Predicate::of( + static fn($value) => $value instanceof $class, + ); } } diff --git a/src/Predicate/OrPredicate.php b/src/Predicate/OrPredicate.php deleted file mode 100644 index 3243b815..00000000 --- a/src/Predicate/OrPredicate.php +++ /dev/null @@ -1,73 +0,0 @@ - - */ -final class OrPredicate implements Predicate -{ - /** - * @param Predicate $a - * @param Predicate $b - */ - private function __construct( - private Predicate $a, - private Predicate $b, - ) { - } - - #[\Override] - public function __invoke(mixed $value): bool - { - return ($this->a)($value) || ($this->b)($value); - } - - /** - * @psalm-pure - * @template T - * @template V - * - * @param Predicate $a - * @param Predicate $b - * - * @return self - */ - #[\NoDiscard] - public static function of(Predicate $a, Predicate $b): self - { - return new self($a, $b); - } - - /** - * @template C - * - * @param Predicate $other - * - * @return self - */ - #[\NoDiscard] - public function or(Predicate $other): self - { - return new self($this, $other); - } - - /** - * @template C - * - * @param Predicate $other - * - * @return AndPredicate - */ - #[\NoDiscard] - public function and(Predicate $other): AndPredicate - { - return AndPredicate::of($this, $other); - } -} diff --git a/src/Sequence.php b/src/Sequence.php index f4137a36..d4a3a5f3 100644 --- a/src/Sequence.php +++ b/src/Sequence.php @@ -7,7 +7,7 @@ * @template-covariant T * @psalm-immutable */ -final class Sequence implements \Countable +final class Sequence { /** * @param Sequence\Implementation $implementation @@ -192,16 +192,6 @@ public function size(): int return $this->implementation->size(); } - /** - * @return int<0, max> - */ - #[\Override] - #[\NoDiscard] - public function count(): int - { - return $this->implementation->size(); - } - /** * Return the element at the given index * @@ -390,21 +380,6 @@ public function contains($element): bool return $this->implementation->contains($element); } - /** - * Return the index for the given element - * - * @deprecated - * - * @param T $element - * - * @return Maybe> - */ - #[\NoDiscard] - public function indexOf($element): Maybe - { - return $this->implementation->indexOf($element); - } - /** * Return the list of indices * @@ -500,14 +475,16 @@ public function pad(int $size, $element): self } /** - * Return a sequence of 2 sequences partitioned according to the given predicate + * Return 2 Sequences partitioned according to the given predicate + * + * The first Sequence contains values that matched the predicate. * * @param callable(T): bool $predicate * - * @return Map> + * @return array{self, self} */ #[\NoDiscard] - public function partition(callable $predicate): Map + public function partition(callable $predicate): array { return $this->implementation->partition($predicate); } @@ -750,6 +727,15 @@ public function find(callable $predicate): Maybe return $this->implementation->find($predicate); } + /** + * @return Sequence\Lookup + */ + #[\NoDiscard] + public function lookup(): Sequence\Lookup + { + return Sequence\Lookup::of($this->implementation); + } + /** * @template R * diff --git a/src/Sequence/Defer.php b/src/Sequence/Defer.php index bd1a8efb..cdb15b34 100644 --- a/src/Sequence/Defer.php +++ b/src/Sequence/Defer.php @@ -79,12 +79,6 @@ public function size(): int return $this->memoize()->size(); } - #[\Override] - public function count(): int - { - return $this->size(); - } - /** * @return Iterator */ @@ -389,42 +383,6 @@ public function contains($element): bool return false; } - /** - * @param T $element - * - * @return Maybe> - */ - #[\Override] - public function indexOf($element): Maybe - { - $captured = $this->capture(); - - return Maybe::defer(static function() use (&$captured, $element) { - $index = 0; - /** - * @psalm-suppress PossiblyNullArgument - * @var Iterator - */ - $values = self::detonate($captured); - $values->rewind(); - - while ($values->valid()) { - if ($values->current() === $element) { - $values->cleanup(); - - /** @var Maybe> */ - return Maybe::just($index); - } - - ++$index; - $values->next(); - } - - /** @var Maybe> */ - return Maybe::nothing(); - }); - } - /** * Return the list of indices * @@ -586,12 +544,12 @@ public function pad(int $size, $element): Implementation /** * @param callable(T): bool $predicate * - * @return Map> + * @return array{Sequence, Sequence} */ #[\Override] - public function partition(callable $predicate): Map + public function partition(callable $predicate): array { - /** @var Map> */ + /** @var array{Sequence, Sequence} */ return $this->memoize()->partition($predicate); } @@ -944,10 +902,9 @@ public function toSet(): Set /** * @psalm-suppress ImpureFunctionCall - * @psalm-suppress DeprecatedMethod */ - return Set::defer( - (static function() use (&$captured): \Generator { + return Set::lazy( + static function() use (&$captured): \Generator { /** * @psalm-suppress PossiblyNullArgument * @var Iterator @@ -959,8 +916,8 @@ public function toSet(): Set yield $values->current(); $values->next(); } - })(), - ); + }, + )->snap(); } #[\Override] diff --git a/src/Sequence/Implementation.php b/src/Sequence/Implementation.php index 99cadcce..04434565 100644 --- a/src/Sequence/Implementation.php +++ b/src/Sequence/Implementation.php @@ -16,7 +16,7 @@ * @template T * @psalm-immutable */ -interface Implementation extends \Countable +interface Implementation { /** * Add the given element at the end of the sequence @@ -135,15 +135,6 @@ public function last(): Maybe; */ public function contains($element): bool; - /** - * Return the index for the given element - * - * @param T $element - * - * @return Maybe> - */ - public function indexOf($element): Maybe; - /** * Return the list of indices * @@ -193,13 +184,13 @@ public function via(callable $map): Sequence; public function pad(int $size, $element): self; /** - * Return a sequence of 2 sequences partitioned according to the given predicate + * Return 2 Sequences partitioned according to the given predicate * * @param callable(T): bool $predicate * - * @return Map> + * @return array{Sequence, Sequence} */ - public function partition(callable $predicate): Map; + public function partition(callable $predicate): array; /** * Slice the sequence diff --git a/src/Sequence/Lazy.php b/src/Sequence/Lazy.php index a8a3a548..12138bd6 100644 --- a/src/Sequence/Lazy.php +++ b/src/Sequence/Lazy.php @@ -73,12 +73,6 @@ public function size(): int return $size; } - #[\Override] - public function count(): int - { - return $this->size(); - } - /** * @return Iterator */ @@ -359,39 +353,6 @@ public function contains($element): bool return false; } - /** - * @param T $element - * - * @return Maybe> - */ - #[\Override] - public function indexOf($element): Maybe - { - $values = $this->values; - - return Maybe::defer(static function() use ($values, $element) { - $index = 0; - $register = RegisterCleanup::noop(); - $iterator = $values($register); - $iterator->rewind(); - - while ($iterator->valid()) { - if ($iterator->current() === $element) { - $register->cleanup(); - - /** @var Maybe> */ - return Maybe::just($index); - } - - ++$index; - $iterator->next(); - } - - /** @var Maybe> */ - return Maybe::nothing(); - }); - } - /** * Return the list of indices * @@ -530,12 +491,12 @@ static function(RegisterCleanup $registerCleanup) use ($values, $size, $element) /** * @param callable(T): bool $predicate * - * @return Map> + * @return array{Sequence, Sequence} */ #[\Override] - public function partition(callable $predicate): Map + public function partition(callable $predicate): array { - /** @var Map> */ + /** @var array{Sequence, Sequence} */ return $this->load()->partition($predicate); } diff --git a/src/Sequence/Lookup.php b/src/Sequence/Lookup.php new file mode 100644 index 00000000..22530fed --- /dev/null +++ b/src/Sequence/Lookup.php @@ -0,0 +1,49 @@ + $implementation + */ + private function __construct( + private Implementation $implementation, + ) { + } + + /** + * @psalm-pure + * @template A + * @internal + * + * @param Implementation $implementation + * + * @return self + */ + public static function of(Implementation $implementation): self + { + return new self($implementation); + } + + /** + * @return Lookup\First + */ + public function first(): Lookup\First + { + return Lookup\First::of($this->implementation); + } + + /** + * @return Lookup\Last + */ + public function last(): Lookup\Last + { + return Lookup\Last::of($this->implementation); + } +} diff --git a/src/Sequence/Lookup/First.php b/src/Sequence/Lookup/First.php new file mode 100644 index 00000000..4049dbd1 --- /dev/null +++ b/src/Sequence/Lookup/First.php @@ -0,0 +1,122 @@ + $implementation + */ + private function __construct( + private Implementation $implementation, + ) { + } + + /** + * @psalm-pure + * @template A + * @internal + * + * @param Implementation $implementation + * + * @return self + */ + public static function of(Implementation $implementation): self + { + return new self($implementation); + } + + /** + * @template U + * + * @param callable(T): Maybe $find + * + * @return Maybe + */ + public function maybe(callable $find): Maybe + { + /** + * @psalm-suppress MixedArgument + * @var Maybe + */ + return $this->implementation->sink( + Maybe::nothing(), + static function($_, $value, $continuation) use ($find) { + $found = $find($value); + + return $found->match( + static fn() => $continuation->stop($found), + static fn() => $continuation->continue($_), + ); + }, + ); + } + + /** + * @template U + * + * @param callable(T): Attempt $find + * + * @return Attempt + */ + public function attempt(\Throwable $default, callable $find): Attempt + { + /** + * @psalm-suppress MixedArgument + * @psalm-suppress MixedArgumentTypeCoercion + * @var Attempt + */ + return $this->implementation->sink( + Attempt::error($default), + static function($_, $value, $continuation) use ($find) { + $found = $find($value); + + return $found->match( + static fn() => $continuation->stop($found), + static fn() => $continuation->continue($_), + ); + }, + ); + } + + /** + * @template U + * @template L + * + * @param L $left + * @param callable(T): Either $find + * + * @return Either + */ + public function either(mixed $left, callable $find): Either + { + /** + * @psalm-suppress MixedArgument + * @psalm-suppress MixedArgumentTypeCoercion + * @var Either + */ + return $this->implementation->sink( + Either::left($left), + static function($_, $value, $continuation) use ($find) { + $found = $find($value); + + return $found->match( + static fn() => $continuation->stop($found), + static fn() => $continuation->continue($_), + ); + }, + ); + } +} diff --git a/src/Sequence/Lookup/Last.php b/src/Sequence/Lookup/Last.php new file mode 100644 index 00000000..a078e7b8 --- /dev/null +++ b/src/Sequence/Lookup/Last.php @@ -0,0 +1,105 @@ + $implementation + */ + private function __construct( + private Implementation $implementation, + ) { + } + + /** + * @psalm-pure + * @template A + * @internal + * + * @param Implementation $implementation + * + * @return self + */ + public static function of(Implementation $implementation): self + { + return new self($implementation); + } + + /** + * @template U + * + * @param callable(T): Maybe $find + * + * @return Maybe + */ + public function maybe(callable $find): Maybe + { + /** + * @psalm-suppress MixedArgument + * @var Maybe + */ + return $this->implementation->reduce( + Maybe::nothing(), + static fn(Maybe $found, $value) => $find($value)->otherwise( + static fn() => $found, + ), + ); + } + + /** + * @template U + * + * @param callable(T): Attempt $find + * + * @return Attempt + */ + public function attempt(\Throwable $default, callable $find): Attempt + { + /** + * @psalm-suppress MixedArgument + * @var Attempt + */ + return $this->implementation->reduce( + Attempt::error($default), + static fn(Attempt $found, $value) => $find($value)->recover( + static fn($e) => $found->mapError(static fn() => $e), + ), + ); + } + + /** + * @template U + * @template L + * + * @param L $left + * @param callable(T): Either $find + * + * @return Either + */ + public function either(mixed $left, callable $find): Either + { + /** + * @psalm-suppress MixedArgument + * @var Either + */ + return $this->implementation->reduce( + Either::left($left), + static fn(Either $found, $value) => $find($value)->otherwise( + static fn($left) => $found->leftMap(static fn(): mixed => $left), + ), + ); + } +} diff --git a/src/Sequence/Primitive.php b/src/Sequence/Primitive.php index 0a654385..3f7bd20d 100644 --- a/src/Sequence/Primitive.php +++ b/src/Sequence/Primitive.php @@ -47,12 +47,6 @@ public function size(): int return \count($this->values); } - #[\Override] - public function count(): int - { - return $this->size(); - } - /** * @return Iterator */ @@ -233,25 +227,6 @@ public function contains($element): bool return \in_array($element, $this->values, true); } - /** - * @param T $element - * - * @return Maybe> - */ - #[\Override] - public function indexOf($element): Maybe - { - $index = \array_search($element, $this->values, true); - - if ($index === false) { - /** @var Maybe> */ - return Maybe::nothing(); - } - - /** @var Maybe> */ - return Maybe::just($index); - } - #[\Override] public function indices(): self { @@ -330,10 +305,10 @@ public function pad(int $size, $element): self /** * @param callable(T): bool $predicate * - * @return Map> + * @return array{Sequence, Sequence} */ #[\Override] - public function partition(callable $predicate): Map + public function partition(callable $predicate): array { /** @var list */ $truthy = []; @@ -349,10 +324,10 @@ public function partition(callable $predicate): Map } } - $true = Sequence::of(...$truthy); - $false = Sequence::of(...$falsy); - - return Map::of([true, $true], [false, $false]); + return [ + Sequence::of(...$truthy), + Sequence::of(...$falsy), + ]; } /** diff --git a/src/Sequence/Snap.php b/src/Sequence/Snap.php index 4b7f626d..5a752563 100644 --- a/src/Sequence/Snap.php +++ b/src/Sequence/Snap.php @@ -51,12 +51,6 @@ public function size(): int return $this->memoize()->size(); } - #[\Override] - public function count(): int - { - return $this->size(); - } - /** * @return Iterator */ @@ -188,17 +182,6 @@ public function contains($element): bool return $this->memoize()->contains($element); } - /** - * @param T $element - * - * @return Maybe> - */ - #[\Override] - public function indexOf($element): Maybe - { - return $this->memoize()->indexOf($element); - } - /** * Return the list of indices * @@ -285,12 +268,12 @@ public function pad(int $size, $element): Implementation /** * @param callable(T): bool $predicate * - * @return Map> + * @return array{Sequence, Sequence} */ #[\Override] - public function partition(callable $predicate): Map + public function partition(callable $predicate): array { - /** @var Map> */ + /** @var array{Sequence, Sequence} */ return $this->memoize()->partition($predicate); } diff --git a/src/Set.php b/src/Set.php index 5d913ab2..01a662bb 100644 --- a/src/Set.php +++ b/src/Set.php @@ -7,7 +7,7 @@ * @template-covariant T * @psalm-immutable */ -final class Set implements \Countable +final class Set { /** * @param Sequence $implementation @@ -50,26 +50,6 @@ public static function of(...$values): self return new self(Sequence::of(...$values)->distinct()); } - /** - * It will load the values inside the generator only upon the first use - * of the set - * - * Use this mode when the amount of data may not fit in memory - * - * @template V - * @psalm-pure - * @deprecated You should use ::snap() instead - * - * @param \Generator $generator - * - * @return self - */ - #[\NoDiscard] - public static function defer(\Generator $generator): self - { - return new self(Sequence::defer($generator)->distinct()); - } - /** * It will call the given function every time a new operation is done on the * set. This means the returned structure may not be truly immutable as @@ -172,16 +152,6 @@ public function size(): int return $this->implementation->size(); } - /** - * @return int<0, max> - */ - #[\Override] - #[\NoDiscard] - public function count(): int - { - return $this->implementation->size(); - } - /** * Intersect this set with the given one * @@ -396,19 +366,23 @@ public function flatMap(callable $map): self } /** - * Return a sequence of 2 sets partitioned according to the given predicate + * Return 2 Sets partitioned according to the given predicate + * + * The first Set contains values that matched the predicate. * * @param callable(T): bool $predicate * - * @return Map> + * @return array{self, self} */ #[\NoDiscard] - public function partition(callable $predicate): Map + public function partition(callable $predicate): array { - return $this - ->implementation - ->partition($predicate) - ->map(static fn($_, $sequence) => $sequence->toSet()); + [$true, $false] = $this->implementation->partition($predicate); + + return [ + $true->toSet(), + $false->toSet(), + ]; } /** diff --git a/src/SideEffect.php b/src/SideEffect.php index 9857ec23..f1e48ccc 100644 --- a/src/SideEffect.php +++ b/src/SideEffect.php @@ -9,16 +9,9 @@ * * @psalm-immutable */ -final class SideEffect +enum SideEffect { - private static ?self $instance = null; - - /** - * @deprecated Use self::identity() instead - */ - public function __construct() - { - } + case identity; /** * @psalm-pure @@ -26,7 +19,6 @@ public function __construct() #[\NoDiscard] public static function identity(): self { - /** @psalm-suppress ImpureStaticProperty This will become an enum in the future */ - return self::$instance ??= new self; + return self::identity; } } diff --git a/src/State.php b/src/State.php deleted file mode 100644 index 9fdef5d2..00000000 --- a/src/State.php +++ /dev/null @@ -1,94 +0,0 @@ - */ - private $run; - - /** - * @param callable(S): Result $run - */ - private function __construct(callable $run) - { - $this->run = $run; - } - - /** - * @psalm-pure - * @template A - * @template B - * - * @param callable(A): Result $run - * - * @return self - */ - #[\NoDiscard] - public static function of(callable $run): self - { - return new self($run); - } - - /** - * @template U - * - * @param callable(T): U $map - * - * @return self - */ - #[\NoDiscard] - public function map(callable $map): self - { - $run = $this->run; - - return new self(static function(mixed $state) use ($run, $map) { - /** @var S $state */ - $result = $run($state); - - return Result::of($result->state(), $map($result->value())); - }); - } - - /** - * @template A - * - * @param callable(T): self $map - * - * @return self - */ - #[\NoDiscard] - public function flatMap(callable $map): self - { - $run = $this->run; - - return new self(static function(mixed $state) use ($run, $map) { - /** @var S $state */ - $result = $run($state); - - return $map($result->value())->run($result->state()); - }); - } - - /** - * @param S $state - * - * @return Result - */ - #[\NoDiscard] - public function run($state): Result - { - /** @psalm-suppress ImpureFunctionCall */ - return ($this->run)($state); - } -} diff --git a/src/State/Result.php b/src/State/Result.php deleted file mode 100644 index 4714df67..00000000 --- a/src/State/Result.php +++ /dev/null @@ -1,54 +0,0 @@ - - */ - public static function of($state, $value): self - { - return new self($state, $value); - } - - /** - * @return S - */ - public function state() - { - return $this->state; - } - - /** - * @return T - */ - public function value() - { - return $this->value; - } -} diff --git a/src/Str.php b/src/Str.php index 77a90ba1..d09c0a07 100644 --- a/src/Str.php +++ b/src/Str.php @@ -444,10 +444,10 @@ public function sprintf(string ...$values): self #[\NoDiscard] public function ucfirst(): self { - return $this - ->substring(0, 1) - ->toUpper() - ->append($this->substring(1)->toString()); + return new self( + \mb_ucfirst($this->value, $this->encoding->toString()), + $this->encoding, + ); } /** @@ -456,10 +456,10 @@ public function ucfirst(): self #[\NoDiscard] public function lcfirst(): self { - return $this - ->substring(0, 1) - ->toLower() - ->append($this->substring(1)->toString()); + return new self( + \mb_lcfirst($this->value, $this->encoding->toString()), + $this->encoding, + ); } /** @@ -511,7 +511,7 @@ public function equals(self $string): bool public function trim(?string $mask = null): self { return new self( - $mask === null ? \trim($this->value) : \trim($this->value, $mask), + \mb_trim($this->value, $mask, $this->encoding->toString()), $this->encoding, ); } @@ -523,7 +523,7 @@ public function trim(?string $mask = null): self public function rightTrim(?string $mask = null): self { return new self( - $mask === null ? \rtrim($this->value) : \rtrim($this->value, $mask), + \mb_rtrim($this->value, $mask, $this->encoding->toString()), $this->encoding, ); } @@ -535,7 +535,7 @@ public function rightTrim(?string $mask = null): self public function leftTrim(?string $mask = null): self { return new self( - $mask === null ? \ltrim($this->value) : \ltrim($this->value, $mask), + \mb_ltrim($this->value, $mask, $this->encoding->toString()), $this->encoding, ); } @@ -555,11 +555,7 @@ public function contains(string|\Stringable $value): bool #[\NoDiscard] public function startsWith(string|\Stringable $value): bool { - if ($value === '') { - return true; - } - - return \mb_strpos($this->value, (string) $value, 0, $this->encoding->toString()) === 0; + return \str_starts_with($this->value, (string) $value); } /** @@ -568,15 +564,7 @@ public function startsWith(string|\Stringable $value): bool #[\NoDiscard] public function endsWith(string|\Stringable $value): bool { - $value = (string) $value; - - if ($value === '') { - return true; - } - - $length = self::of($value, $this->encoding)->length(); - - return $this->takeEnd($length)->toString() === $value; + return \str_ends_with($this->value, (string) $value); } /** @@ -613,11 +601,12 @@ public function flatMap(callable $map): self */ private function pad(int $length, string|\Stringable $character, int $direction): self { - return new self(\str_pad( + return new self(\mb_str_pad( $this->value, $length, (string) $character, $direction, + $this->encoding->toString(), ), $this->encoding); } } diff --git a/tests/FoldTest.php b/tests/FoldTest.php deleted file mode 100644 index d1af8916..00000000 --- a/tests/FoldTest.php +++ /dev/null @@ -1,235 +0,0 @@ -forAll( - Set::type(), - Set::type(), - ) - ->prove(function($source, $mapped) { - $fold = Fold::with($source) - ->map(function($in) use ($source, $mapped) { - $this->assertSame($source, $in); - - return $mapped; - }) - ->match( - static fn($with) => $with, - static fn() => null, - static fn() => null, - ); - - $this->assertSame($mapped, $fold); - - $fold = Fold::result($source) - ->map(static fn() => $mapped) - ->match( - static fn() => null, - static fn($result) => $result, - static fn() => null, - ); - - $this->assertSame($source, $fold); - - $fold = Fold::fail($source) - ->map(static fn() => $mapped) - ->match( - static fn() => null, - static fn() => null, - static fn($failure) => $failure, - ); - - $this->assertSame($source, $fold); - }); - } - - public function testFlatMap(): BlackBox\Proof - { - return $this - ->forAll( - Set::type(), - Set::compose( - static fn($value, $type) => $type($value), - Set::type(), - Set::of( - static fn($value) => Fold::with($value), - static fn($value) => Fold::result($value), - static fn($value) => Fold::fail($value), - ), - ), - ) - ->prove(function($source, $mapped) { - $fold = Fold::with($source) - ->flatMap(function($in) use ($source, $mapped) { - $this->assertSame($source, $in); - - return $mapped; - }); - - $this->assertSame($mapped, $fold); - - $fold = Fold::result($source) - ->flatMap(static fn() => $mapped) - ->match( - static fn() => null, - static fn($result) => $result, - static fn() => null, - ); - - $this->assertSame($source, $fold); - - $fold = Fold::fail($source) - ->flatMap(static fn() => $mapped) - ->match( - static fn() => null, - static fn() => null, - static fn($failure) => $failure, - ); - - $this->assertSame($source, $fold); - }); - } - - public function testMaybe(): BlackBox\Proof - { - return $this - ->forAll(Set::type()) - ->prove(function($source) { - $this->assertFalse( - Fold::with($source) - ->maybe() - ->match( - static fn() => true, - static fn() => false, - ), - ); - $this->assertSame( - $source, - Fold::result($source) - ->maybe() - ->match( - static fn($either) => $either->match( - static fn($result) => $result, - static fn() => null, - ), - static fn() => null, - ), - ); - $this->assertSame( - $source, - Fold::fail($source) - ->maybe() - ->match( - static fn($either) => $either->match( - static fn() => null, - static fn($fail) => $fail, - ), - static fn() => null, - ), - ); - }); - } - - public function testMapResult(): BlackBox\Proof - { - return $this - ->forAll( - Set::type(), - Set::type(), - ) - ->prove(function($source, $mapped) { - $fold = Fold::with($source) - ->mapResult(static fn() => $mapped) - ->match( - static fn($with) => $with, - static fn() => null, - static fn() => null, - ); - - $this->assertSame($source, $fold); - - $fold = Fold::result($source) - ->mapResult(function($in) use ($source, $mapped) { - $this->assertSame($source, $in); - - return $mapped; - }) - ->match( - static fn() => null, - static fn($result) => $result, - static fn() => null, - ); - - $this->assertSame($mapped, $fold); - - $fold = Fold::fail($source) - ->mapResult(static fn() => $mapped) - ->match( - static fn() => null, - static fn() => null, - static fn($failure) => $failure, - ); - - $this->assertSame($source, $fold); - }); - } - - public function testMapFailure(): BlackBox\Proof - { - return $this - ->forAll( - Set::type(), - Set::type(), - ) - ->prove(function($source, $mapped) { - $fold = Fold::with($source) - ->mapFailure(static fn() => $mapped) - ->match( - static fn($with) => $with, - static fn() => null, - static fn() => null, - ); - - $this->assertSame($source, $fold); - - $fold = Fold::result($source) - ->mapFailure(static fn() => $mapped) - ->match( - static fn() => null, - static fn($result) => $result, - static fn() => null, - ); - - $this->assertSame($source, $fold); - - $fold = Fold::fail($source) - ->mapFailure(function($in) use ($source, $mapped) { - $this->assertSame($source, $in); - - return $mapped; - }) - ->match( - static fn() => null, - static fn() => null, - static fn($failure) => $failure, - ); - - $this->assertSame($mapped, $fold); - }); - } -} diff --git a/tests/Map/DoubleIndexTest.php b/tests/Map/DoubleIndexTest.php index 44e65d60..b2a10620 100644 --- a/tests/Map/DoubleIndexTest.php +++ b/tests/Map/DoubleIndexTest.php @@ -19,7 +19,6 @@ public function testInterface() $m = new DoubleIndex; $this->assertInstanceOf(Implementation::class, $m); - $this->assertInstanceOf(\Countable::class, $m); } public function testPut() @@ -311,31 +310,25 @@ public function testPartition() (3, 4) (4, 5); - $p = $m->partition(static function(int $i, int $v) { + [$true, $false] = $m->partition(static function(int $i, int $v) { return ($i + $v) % 3 === 0; }); - $this->assertInstanceOf(Map::class, $p); - $this->assertNotSame($p, $m); - $this->assertSame( - [true, false], - $p->keys()->toList(), - ); $this->assertSame( [1, 4], - $this->get($p, true)->keys()->toList(), + $true->keys()->toList(), ); $this->assertSame( [2, 5], - $this->get($p, true)->values()->toList(), + $true->values()->toList(), ); $this->assertSame( [0, 2, 3], - $this->get($p, false)->keys()->toList(), + $false->keys()->toList(), ); $this->assertSame( [1, 3, 4], - $this->get($p, false)->values()->toList(), + $false->values()->toList(), ); } diff --git a/tests/Map/ObjectKeysTest.php b/tests/Map/ObjectKeysTest.php index 19c50b66..630fbbbf 100644 --- a/tests/Map/ObjectKeysTest.php +++ b/tests/Map/ObjectKeysTest.php @@ -20,7 +20,6 @@ public function testInterface() $m = new ObjectKeys; $this->assertInstanceOf(Implementation::class, $m); - $this->assertInstanceOf(\Countable::class, $m); } public function testPut() @@ -281,31 +280,25 @@ public function testPartition() ($d = new \stdClass, 4) ($e = new \stdClass, 5); - $p = $m->partition(static function(\stdClass $i, int $v) { + [$true, $false] = $m->partition(static function(\stdClass $i, int $v) { return $v % 2 === 0; }); - $this->assertInstanceOf(Map::class, $p); - $this->assertNotSame($p, $m); - $this->assertSame( - [true, false], - $p->keys()->toList(), - ); $this->assertSame( [$b, $d], - $this->get($p, true)->keys()->toList(), + $true->keys()->toList(), ); $this->assertSame( [2, 4], - $this->get($p, true)->values()->toList(), + $true->values()->toList(), ); $this->assertSame( [$a, $c, $e], - $this->get($p, false)->keys()->toList(), + $false->keys()->toList(), ); $this->assertSame( [1, 3, 5], - $this->get($p, false)->values()->toList(), + $false->values()->toList(), ); } @@ -336,7 +329,7 @@ public function testSwitchImplementationWhenAddingNonKeyObject() $map = (new ObjectKeys)(1, 2); $this->assertInstanceOf(DoubleIndex::class, $map); - $this->assertCount(1, $map); + $this->assertSame(1, $map->size()); } public function testFind() diff --git a/tests/Map/PrimitiveTest.php b/tests/Map/PrimitiveTest.php index 23256ca6..40a46563 100644 --- a/tests/Map/PrimitiveTest.php +++ b/tests/Map/PrimitiveTest.php @@ -19,7 +19,6 @@ public function testInterface() $m = new Primitive; $this->assertInstanceOf(Implementation::class, $m); - $this->assertInstanceOf(\Countable::class, $m); } public function testPut() @@ -301,31 +300,25 @@ public function testPartition() (3, 4) (4, 5); - $p = $m->partition(static function(int $i, int $v) { + [$true, $false] = $m->partition(static function(int $i, int $v) { return ($i + $v) % 3 === 0; }); - $this->assertInstanceOf(Map::class, $p); - $this->assertNotSame($p, $m); - $this->assertSame( - [true, false], - $p->keys()->toList(), - ); $this->assertSame( [1, 4], - $this->get($p, true)->keys()->toList(), + $true->keys()->toList(), ); $this->assertSame( [2, 5], - $this->get($p, true)->values()->toList(), + $true->values()->toList(), ); $this->assertSame( [0, 2, 3], - $this->get($p, false)->keys()->toList(), + $false->keys()->toList(), ); $this->assertSame( [1, 3, 4], - $this->get($p, false)->values()->toList(), + $false->values()->toList(), ); } diff --git a/tests/MapTest.php b/tests/MapTest.php index 14c30607..713f7bc2 100644 --- a/tests/MapTest.php +++ b/tests/MapTest.php @@ -12,13 +12,6 @@ class MapTest extends TestCase { - public function testInterface() - { - $m = Map::of(); - - $this->assertInstanceOf(\Countable::class, $m); - } - public function testOf() { $map = Map::of() @@ -194,7 +187,7 @@ public function testForeach() ->put(3, 4); $count = 0; - $m->foreach(function(int $key, int $value) use (&$count) { + $_ = $m->foreach(function(int $key, int $value) use (&$count) { $this->assertSame($count, $key); $this->assertSame($value, $key + 1); ++$count; @@ -368,31 +361,25 @@ public function testPartition() ->put(3, 4) ->put(4, 5); - $p = $m->partition(static function(int $i, int $v) { + [$true, $false] = $m->partition(static function(int $i, int $v) { return ($i + $v) % 3 === 0; }); - $this->assertInstanceOf(Map::class, $p); - $this->assertNotSame($p, $m); - $this->assertSame( - [true, false], - $p->keys()->toList(), - ); $this->assertSame( [1, 4], - $this->get($p, true)->keys()->toList(), + $true->keys()->toList(), ); $this->assertSame( [2, 5], - $this->get($p, true)->values()->toList(), + $true->values()->toList(), ); $this->assertSame( [0, 2, 3], - $this->get($p, false)->keys()->toList(), + $false->keys()->toList(), ); $this->assertSame( [1, 3, 4], - $this->get($p, false)->values()->toList(), + $false->values()->toList(), ); } diff --git a/tests/MaybeTest.php b/tests/MaybeTest.php index 4306a6a0..93f7ca46 100644 --- a/tests/MaybeTest.php +++ b/tests/MaybeTest.php @@ -430,7 +430,7 @@ public function testAllMapNotCalledWhenOneNothingIsPresent(): BlackBox\Proof $this->assertInstanceOf(Maybe\Comprehension::class, $comprehension); $called = false; - $comprehension->map(static function(...$args) use (&$called) { + $_ = $comprehension->map(static function(...$args) use (&$called) { $called = true; return $args[0]; @@ -453,7 +453,7 @@ public function testAllFlatMapNotCalledWhenOneNothingIsPresent(): BlackBox\Proof $this->assertInstanceOf(Maybe\Comprehension::class, $comprehension); $called = false; - $comprehension->flatMap(static function(...$args) use (&$called) { + $_ = $comprehension->flatMap(static function(...$args) use (&$called) { $called = true; return Maybe::just($args[0]); @@ -599,8 +599,8 @@ public function testMemoize() public function testToSequence() { - $this->assertCount(0, Maybe::nothing()->toSequence()); - $this->assertCount(0, Maybe::defer(static fn() => Maybe::nothing())->toSequence()); + $this->assertSame(0, Maybe::nothing()->toSequence()->size()); + $this->assertSame(0, Maybe::defer(static fn() => Maybe::nothing())->toSequence()->size()); $this ->forAll($this->value()) diff --git a/tests/RegExpTest.php b/tests/RegExpTest.php index ad1a6c41..a7c609b0 100644 --- a/tests/RegExpTest.php +++ b/tests/RegExpTest.php @@ -32,7 +32,7 @@ public function testThrowWhenInvalidRegexp() { $this->expectException(LogicException::class); - RegExp::of('/foo'); + $_ = RegExp::of('/foo'); } public function testMatches() diff --git a/tests/Sequence/DeferTest.php b/tests/Sequence/DeferTest.php index 35b97fb7..ff595793 100644 --- a/tests/Sequence/DeferTest.php +++ b/tests/Sequence/DeferTest.php @@ -41,7 +41,6 @@ public function testSize() })()); $this->assertSame(2, $sequence->size()); - $this->assertSame(2, $sequence->count()); } public function testIterator() @@ -241,7 +240,7 @@ public function testGroupBy() $this->assertSame([1, 2, 3, 4], \iterator_to_array($sequence->iterator())); $this->assertInstanceOf(Map::class, $groups); - $this->assertCount(2, $groups); + $this->assertSame(2, $groups->size()); $this->assertSame([2, 4], $this->get($groups, 0)->toList()); $this->assertSame([1, 3], $this->get($groups, 1)->toList()); } @@ -341,46 +340,6 @@ public function testContains() $this->assertFalse($sequence->contains(4)); } - public function testIndexOf() - { - $sequence = new Defer((static function() { - yield 1; - yield 2; - yield 4; - })()); - - $this->assertSame( - 1, - $sequence->indexOf(2)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - $this->assertSame( - 2, - $sequence->indexOf(4)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - } - - public function testReturnNothingWhenTryingToAccessIndexOfUnknownValue() - { - $sequence = new Defer((static function() { - if (false) { - yield 1; - } - })()); - - $this->assertNull( - $sequence->indexOf(1)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - } - public function testIndices() { $loaded = false; @@ -458,13 +417,11 @@ public function testPartition() yield 3; yield 4; })()); - $partition = $sequence->partition(static fn($i) => $i % 2 === 0); + [$true, $false] = $sequence->partition(static fn($i) => $i % 2 === 0); $this->assertSame([1, 2, 3, 4], \iterator_to_array($sequence->iterator())); - $this->assertInstanceOf(Map::class, $partition); - $this->assertCount(2, $partition); - $this->assertSame([2, 4], $this->get($partition, true)->toList()); - $this->assertSame([1, 3], $this->get($partition, false)->toList()); + $this->assertSame([2, 4], $true->toList()); + $this->assertSame([1, 3], $false->toList()); } public function testSlice() diff --git a/tests/Sequence/LazyTest.php b/tests/Sequence/LazyTest.php index 48972a96..b67ee9ad 100644 --- a/tests/Sequence/LazyTest.php +++ b/tests/Sequence/LazyTest.php @@ -41,7 +41,6 @@ public function testSize() }); $this->assertSame(2, $sequence->size()); - $this->assertSame(2, $sequence->count()); } public function testIterator() @@ -247,7 +246,7 @@ public function testGroupBy() $this->assertSame([1, 2, 3, 4], \iterator_to_array($sequence->iterator())); $this->assertInstanceOf(Map::class, $groups); - $this->assertCount(2, $groups); + $this->assertSame(2, $groups->size()); $this->assertSame([2, 4], $this->get($groups, 0)->toList()); $this->assertSame([1, 3], $this->get($groups, 1)->toList()); } @@ -347,46 +346,6 @@ public function testContains() $this->assertFalse($sequence->contains(4)); } - public function testIndexOf() - { - $sequence = new Lazy(static function() { - yield 1; - yield 2; - yield 4; - }); - - $this->assertSame( - 1, - $sequence->indexOf(2)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - $this->assertSame( - 2, - $sequence->indexOf(4)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - } - - public function testReturnNothingWhenTryingToAccessIndexOfUnknownValue() - { - $sequence = new Lazy(static function() { - if (false) { - yield 1; - } - }); - - $this->assertNull( - $sequence->indexOf(1)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - } - public function testIndices() { $loaded = false; @@ -464,13 +423,11 @@ public function testPartition() yield 3; yield 4; }); - $partition = $sequence->partition(static fn($i) => $i % 2 === 0); + [$true, $false] = $sequence->partition(static fn($i) => $i % 2 === 0); $this->assertSame([1, 2, 3, 4], \iterator_to_array($sequence->iterator())); - $this->assertInstanceOf(Map::class, $partition); - $this->assertCount(2, $partition); - $this->assertSame([2, 4], $this->get($partition, true)->toList()); - $this->assertSame([1, 3], $this->get($partition, false)->toList()); + $this->assertSame([2, 4], $true->toList()); + $this->assertSame([1, 3], $false->toList()); } public function testSlice() @@ -790,28 +747,6 @@ public function testCallCleanupWhenElementBeingLookedForIsFoundBeforeTheEndOfGen $this->assertTrue($cleanupCalled); } - public function testCallCleanupWhenIndexOfElementBeingLookedForIsFoundBeforeTheEndOfGenerator() - { - $cleanupCalled = false; - $sequence = new Lazy(static function($registerCleanup) use (&$cleanupCalled) { - $registerCleanup(static function() use (&$cleanupCalled) { - $cleanupCalled = true; - }); - yield 2; - yield 3; - yield 4; - yield 5; - }); - - $this->assertFalse($cleanupCalled); - $index = $sequence->indexOf(3)->match( - static fn($index) => $index, - static fn() => null, - ); - $this->assertSame(1, $index); - $this->assertTrue($cleanupCalled); - } - public function testCallCleanupWhenTakingLessElementsThanContainedInTheGenerator() { $cleanupCalled = false; diff --git a/tests/Sequence/PrimitiveTest.php b/tests/Sequence/PrimitiveTest.php index a886c365..aa25dd59 100644 --- a/tests/Sequence/PrimitiveTest.php +++ b/tests/Sequence/PrimitiveTest.php @@ -24,7 +24,6 @@ public function testInterface() public function testSize() { $this->assertSame(2, (new Primitive([1, 1]))->size()); - $this->assertSame(2, (new Primitive([1, 1]))->count()); } public function testIterator() @@ -136,7 +135,7 @@ public function testGroupBy() $this->assertSame([1, 2, 3, 4], \iterator_to_array($sequence->iterator())); $this->assertInstanceOf(Map::class, $groups); - $this->assertCount(2, $groups); + $this->assertSame(2, $groups->size()); $this->assertSame([2, 4], $this->get($groups, 0)->toList()); $this->assertSame([1, 3], $this->get($groups, 1)->toList()); } @@ -191,36 +190,6 @@ public function testContains() $this->assertFalse($sequence->contains(4)); } - public function testIndexOf() - { - $sequence = new Primitive([1, 2, 4]); - - $this->assertSame( - 1, - $sequence->indexOf(2)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - $this->assertSame( - 2, - $sequence->indexOf(4)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - } - - public function testReturnNothingWhenTryingToAccessIndexOfUnknownValue() - { - $this->assertNull( - (new Primitive)->indexOf(1)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - } - public function testIndices() { $a = new Primitive(['1', '2']); @@ -267,13 +236,11 @@ public function testPad() public function testPartition() { $sequence = new Primitive([1, 2, 3, 4]); - $partition = $sequence->partition(static fn($i) => $i % 2 === 0); + [$true, $false] = $sequence->partition(static fn($i) => $i % 2 === 0); $this->assertSame([1, 2, 3, 4], \iterator_to_array($sequence->iterator())); - $this->assertInstanceOf(Map::class, $partition); - $this->assertCount(2, $partition); - $this->assertSame([2, 4], $this->get($partition, true)->toList()); - $this->assertSame([1, 3], $this->get($partition, false)->toList()); + $this->assertSame([2, 4], $true->toList()); + $this->assertSame([1, 3], $false->toList()); } public function testSlice() diff --git a/tests/SequenceTest.php b/tests/SequenceTest.php index 5b07f460..2d930a0d 100644 --- a/tests/SequenceTest.php +++ b/tests/SequenceTest.php @@ -26,7 +26,6 @@ public function testInterface() { $sequence = Sequence::of(); - $this->assertInstanceOf(\Countable::class, $sequence); $this->assertSame([], $sequence->toList()); } @@ -146,11 +145,12 @@ public function testSize() public function testCount() { - $this->assertCount( + $this->assertSame( 2, Sequence::of() ->add(1) - ->add(2), + ->add(2) + ->size(), ); } @@ -309,7 +309,7 @@ public function testGroupBy() }); $this->assertInstanceOf(Map::class, $map); - $this->assertCount(3, $map); + $this->assertSame(3, $map->size()); $this->assertSame([3], $this->get($map, 0)->toList()); $this->assertSame([1, 4], $this->get($map, 1)->toList()); $this->assertSame([2], $this->get($map, 2)->toList()); @@ -370,30 +370,6 @@ public function testContains() $this->assertFalse($sequence->contains(5)); } - public function testIndexOf() - { - $sequence = Sequence::of() - ->add(1) - ->add(2) - ->add(3) - ->add(4); - - $this->assertSame( - 0, - $sequence->indexOf(1)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - $this->assertSame( - 3, - $sequence->indexOf(4)->match( - static fn($value) => $value, - static fn() => null, - ), - ); - } - public function testIndices() { $sequence = Sequence::of() @@ -494,7 +470,7 @@ public function testPad() public function testPartition() { - $map = Sequence::of() + [$true, $false] = Sequence::of() ->add(1) ->add(2) ->add(3) @@ -503,9 +479,8 @@ public function testPartition() return $value % 2 === 0; }); - $this->assertInstanceOf(Map::class, $map); - $this->assertSame([2, 4], $this->get($map, true)->toList()); - $this->assertSame([1, 3], $this->get($map, false)->toList()); + $this->assertSame([2, 4], $true->toList()); + $this->assertSame([1, 3], $false->toList()); } public function testSlice() @@ -1045,7 +1020,7 @@ public function testSafeguard() $stop = new \Exception; try { - Sequence::of(1, 2, 3, 1)->safeguard( + $_ = Sequence::of(1, 2, 3, 1)->safeguard( Set::of(), static fn($unique, $value) => match ($unique->contains($value)) { true => throw $stop, @@ -1073,7 +1048,7 @@ public function testSafeguard() }, ); $this->assertFalse($loaded); - $sequence->toList(); + $_ = $sequence->toList(); $this->fail('it should throw'); } catch (\Exception $e) { $this->assertSame($stop, $e); @@ -1095,7 +1070,7 @@ public function testSafeguard() }, ); $this->assertFalse($loaded); - $sequence->toList(); + $_ = $sequence->toList(); $this->fail('it should throw'); } catch (\Exception $e) { $this->assertSame($stop, $e); @@ -1159,12 +1134,12 @@ public function testAggregate() $lines->toList(), ); $this->assertSame(1, $loaded); - $lines->toList(); + $_ = $lines->toList(); $this->assertSame(2, $loaded); }); try { - Str::of("foo\nbar\nbaz") + $_ = Str::of("foo\nbar\nbaz") ->chunk(1) ->aggregate(static fn() => Sequence::of()) ->toList(); diff --git a/tests/Set/DeferTest.php b/tests/Set/DeferTest.php deleted file mode 100644 index 7cd18717..00000000 --- a/tests/Set/DeferTest.php +++ /dev/null @@ -1,412 +0,0 @@ -assertInstanceOf( - Set::class, - Set::defer((static function() { - yield; - })()), - ); - } - - public function testSize() - { - $set = Set::defer((static function() { - yield 1; - yield 2; - })()); - - $this->assertSame(2, $set->size()); - $this->assertSame(2, $set->count()); - } - - public function testIterator() - { - $set = Set::defer((static function() { - yield 1; - yield 2; - })()); - - $this->assertSame([1, 2], $set->unsorted()->toList()); - } - - public function testIntersect() - { - $aLoaded = false; - $bLoaded = false; - $a = Set::defer((static function() use (&$aLoaded) { - yield 1; - yield 2; - $aLoaded = true; - })()); - $b = Set::defer((static function() use (&$bLoaded) { - yield 2; - yield 3; - $bLoaded = true; - })()); - $c = $a->intersect($b); - - $this->assertFalse($aLoaded); - $this->assertFalse($bLoaded); - $this->assertSame([1, 2], $a->unsorted()->toList()); - $this->assertSame([2, 3], $b->unsorted()->toList()); - $this->assertInstanceOf(Set::class, $c); - $this->assertSame([2], $c->unsorted()->toList()); - $this->assertTrue($aLoaded); - $this->assertTrue($bLoaded); - } - - public function testAdd() - { - $loaded = false; - $a = Set::defer((static function() use (&$loaded) { - yield 1; - $loaded = true; - })()); - $b = ($a)(2); - - $this->assertFalse($loaded); - $this->assertSame([1], $a->unsorted()->toList()); - $this->assertInstanceOf(Set::class, $b); - $this->assertSame([1, 2], $b->unsorted()->toList()); - $this->assertSame([1, 2], ($b)(2)->unsorted()->toList()); - $this->assertTrue($loaded); - } - - public function testContains() - { - $set = Set::defer((static function() { - yield 1; - })()); - - $this->assertTrue($set->contains(1)); - $this->assertFalse($set->contains(2)); - } - - public function testRemove() - { - $a = Set::defer((static function() { - yield 1; - yield 2; - yield 3; - yield 4; - })()); - $b = $a->remove(3); - - $this->assertSame([1, 2, 3, 4], $a->unsorted()->toList()); - $this->assertInstanceOf(Set::class, $b); - $this->assertSame([1, 2, 4], $b->unsorted()->toList()); - $this->assertSame([1, 2, 3, 4], $a->remove(5)->unsorted()->toList()); - } - - public function testDiff() - { - $aLoaded = false; - $a = Set::defer((static function() use (&$aLoaded) { - yield 1; - yield 2; - yield 3; - $aLoaded = true; - })()); - $bLoaded = false; - $b = Set::defer((static function() use (&$bLoaded) { - yield 2; - yield 4; - $bLoaded = true; - })()); - $c = $a->diff($b); - - $this->assertFalse($aLoaded); - $this->assertFalse($bLoaded); - $this->assertSame([1, 2, 3], $a->unsorted()->toList()); - $this->assertSame([2, 4], $b->unsorted()->toList()); - $this->assertInstanceOf(Set::class, $c); - $this->assertSame([1, 3], $c->unsorted()->toList()); - $this->assertTrue($aLoaded); - $this->assertTrue($bLoaded); - } - - public function testEquals() - { - $a = Set::defer((static function() { - yield 1; - yield 2; - })()); - $aBis = Set::defer((static function() { - yield 1; - yield 2; - })()); - $b = Set::defer((static function() { - yield 1; - })()); - $c = Set::defer((static function() { - yield 1; - yield 2; - yield 3; - })()); - - $this->assertTrue($a->equals($a)); - $this->assertTrue($a->equals($aBis)); - $this->assertFalse($a->equals($b)); - $this->assertFalse($a->equals($c)); - } - - public function testFilter() - { - $loaded = false; - $a = Set::defer((static function() use (&$loaded) { - yield 1; - yield 2; - yield 3; - yield 4; - $loaded = true; - })()); - $b = $a->filter(static fn($i) => $i % 2 === 0); - - $this->assertFalse($loaded); - $this->assertSame([1, 2, 3, 4], $a->unsorted()->toList()); - $this->assertInstanceOf(Set::class, $b); - $this->assertSame([2, 4], $b->unsorted()->toList()); - $this->assertTrue($loaded); - } - - public function testForeach() - { - $set = Set::defer((static function() { - yield 1; - yield 2; - yield 3; - yield 4; - })()); - $calls = 0; - $sum = 0; - - $this->assertInstanceOf( - SideEffect::class, - $set->foreach(static function($i) use (&$calls, &$sum) { - ++$calls; - $sum += $i; - }), - ); - - $this->assertSame(4, $calls); - $this->assertSame(10, $sum); - } - - public function testGroupBy() - { - $set = Set::defer((static function() { - yield 1; - yield 2; - yield 3; - yield 4; - })()); - $groups = $set->groupBy(static fn($i) => $i % 2); - - $this->assertSame([1, 2, 3, 4], $set->unsorted()->toList()); - $this->assertInstanceOf(Map::class, $groups); - $this->assertCount(2, $groups); - $this->assertSame([2, 4], $this->get($groups, 0)->toList()); - $this->assertSame([1, 3], $this->get($groups, 1)->toList()); - } - - public function testMap() - { - $loaded = false; - $a = Set::defer((static function() use (&$loaded) { - yield 1; - yield 2; - yield 3; - $loaded = true; - })()); - $b = $a->map(static fn($i) => $i * 2); - - $this->assertFalse($loaded); - $this->assertSame([1, 2, 3], $a->unsorted()->toList()); - $this->assertInstanceOf(Set::class, $b); - $this->assertSame([2, 4, 6], $b->unsorted()->toList()); - $this->assertTrue($loaded); - } - - public function testMapDoesntIntroduceDuplicates() - { - $set = Set::defer((static function() { - yield 1; - yield 2; - yield 3; - })()); - - $this->assertSame( - [1], - $set->map(static fn() => 1)->unsorted()->toList(), - ); - } - - public function testPartition() - { - $set = Set::defer((static function() { - yield 1; - yield 2; - yield 3; - yield 4; - })()); - $groups = $set->partition(static fn($i) => $i % 2 === 0); - - $this->assertSame([1, 2, 3, 4], $set->unsorted()->toList()); - $this->assertInstanceOf(Map::class, $groups); - $this->assertCount(2, $groups); - $this->assertSame([2, 4], $this->get($groups, true)->toList()); - $this->assertSame([1, 3], $this->get($groups, false)->toList()); - } - - public function testSort() - { - $loaded = false; - $set = Set::defer((static function() use (&$loaded) { - yield 1; - yield 4; - yield 3; - yield 2; - $loaded = true; - })()); - $sorted = $set->sort(static fn($a, $b) => $a > $b ? 1 : -1); - - $this->assertFalse($loaded); - $this->assertSame([1, 4, 3, 2], $set->unsorted()->toList()); - $this->assertInstanceOf(Sequence::class, $sorted); - $this->assertSame([1, 2, 3, 4], $sorted->toList()); - $this->assertTrue($loaded); - } - - public function testMerge() - { - $aLoaded = false; - $a = Set::defer((static function() use (&$aLoaded) { - yield 1; - yield 2; - $aLoaded = true; - })()); - $bLoaded = false; - $b = Set::defer((static function() use (&$bLoaded) { - yield 2; - yield 3; - $bLoaded = true; - })()); - $c = $a->merge($b); - - $this->assertFalse($aLoaded); - $this->assertFalse($bLoaded); - $this->assertSame([1, 2], $a->unsorted()->toList()); - $this->assertSame([2, 3], $b->unsorted()->toList()); - $this->assertInstanceOf(Set::class, $c); - $this->assertSame([1, 2, 3], $c->unsorted()->toList()); - $this->assertTrue($aLoaded); - $this->assertTrue($bLoaded); - } - - public function testReduce() - { - $set = Set::defer((static function() { - yield 1; - yield 2; - yield 3; - yield 4; - })()); - - $this->assertSame(10, $set->reduce(0, static fn($sum, $i) => $sum + $i)); - } - - public function testClear() - { - $a = Set::defer((static function() { - yield 1; - })()); - $b = $a->clear(); - - $this->assertSame([1], $a->unsorted()->toList()); - $this->assertInstanceOf(Set::class, $b); - $this->assertSame([], $b->unsorted()->toList()); - } - - public function testEmpty() - { - $a = Set::defer((static function() { - yield 1; - })()); - $b = Set::defer((static function() { - if (false) { - yield 1; - } - })()); - - $this->assertTrue($b->empty()); - $this->assertFalse($a->empty()); - } - - public function testFind() - { - $count = 0; - $sequence = Set::defer((static function() use (&$count) { - ++$count; - yield 1; - ++$count; - yield 2; - ++$count; - yield 3; - })()); - - $this->assertSame( - 1, - $sequence->find(static fn($i) => $i === 1)->match( - static fn($i) => $i, - static fn() => null, - ), - ); - $this->assertSame(1, $count); - $this->assertSame( - 2, - $sequence->find(static fn($i) => $i === 2)->match( - static fn($i) => $i, - static fn() => null, - ), - ); - $this->assertSame(2, $count); - $this->assertSame( - 3, - $sequence->find(static fn($i) => $i === 3)->match( - static fn($i) => $i, - static fn() => null, - ), - ); - $this->assertSame(3, $count); - - $this->assertNull( - $sequence->find(static fn($i) => $i === 0)->match( - static fn($i) => $i, - static fn() => null, - ), - ); - } - - public function get($map, $index) - { - return $map->get($index)->match( - static fn($value) => $value, - static fn() => null, - ); - } -} diff --git a/tests/Set/LazyTest.php b/tests/Set/LazyTest.php index 50329eb3..e9612aa5 100644 --- a/tests/Set/LazyTest.php +++ b/tests/Set/LazyTest.php @@ -31,7 +31,6 @@ public function testSize() }); $this->assertSame(2, $set->size()); - $this->assertSame(2, $set->count()); } public function testIterator() @@ -219,7 +218,7 @@ public function testGroupBy() $this->assertSame([1, 2, 3, 4], $set->unsorted()->toList()); $this->assertInstanceOf(Map::class, $groups); - $this->assertCount(2, $groups); + $this->assertSame(2, $groups->size()); $this->assertSame([2, 4], $this->get($groups, 0)->toList()); $this->assertSame([1, 3], $this->get($groups, 1)->toList()); } @@ -264,13 +263,11 @@ public function testPartition() yield 3; yield 4; }); - $groups = $set->partition(static fn($i) => $i % 2 === 0); + [$true, $false] = $set->partition(static fn($i) => $i % 2 === 0); $this->assertSame([1, 2, 3, 4], $set->unsorted()->toList()); - $this->assertInstanceOf(Map::class, $groups); - $this->assertCount(2, $groups); - $this->assertSame([2, 4], $this->get($groups, true)->toList()); - $this->assertSame([1, 3], $this->get($groups, false)->toList()); + $this->assertSame([2, 4], $true->toList()); + $this->assertSame([1, 3], $false->toList()); } public function testSort() diff --git a/tests/Set/PrimitiveTest.php b/tests/Set/PrimitiveTest.php index 6b9b58f4..9e0a31ff 100644 --- a/tests/Set/PrimitiveTest.php +++ b/tests/Set/PrimitiveTest.php @@ -24,7 +24,6 @@ public function testInterface() public function testSize() { $this->assertSame(2, Set::of(1, 2)->size()); - $this->assertSame(2, Set::of(1, 2)->count()); } public function testIterator() @@ -128,7 +127,7 @@ public function testGroupBy() $this->assertSame([1, 2, 3, 4], $set->unsorted()->toList()); $this->assertInstanceOf(Map::class, $groups); - $this->assertCount(2, $groups); + $this->assertSame(2, $groups->size()); $this->assertSame([2, 4], $this->get($groups, 0)->toList()); $this->assertSame([1, 3], $this->get($groups, 1)->toList()); } @@ -146,13 +145,11 @@ public function testMap() public function testPartition() { $set = Set::of(1, 2, 3, 4); - $groups = $set->partition(static fn($i) => $i % 2 === 0); + [$true, $false] = $set->partition(static fn($i) => $i % 2 === 0); $this->assertSame([1, 2, 3, 4], $set->unsorted()->toList()); - $this->assertInstanceOf(Map::class, $groups); - $this->assertCount(2, $groups); - $this->assertSame([2, 4], $this->get($groups, true)->toList()); - $this->assertSame([1, 3], $this->get($groups, false)->toList()); + $this->assertSame([2, 4], $true->toList()); + $this->assertSame([1, 3], $false->toList()); } public function testSort() diff --git a/tests/SetTest.php b/tests/SetTest.php index 0f0da2dd..0900bd2d 100644 --- a/tests/SetTest.php +++ b/tests/SetTest.php @@ -13,11 +13,6 @@ class SetTest extends TestCase { - public function testInterface() - { - $this->assertInstanceOf(\Countable::class, Set::of()); - } - public function testOf() { $this->assertTrue( @@ -30,22 +25,6 @@ public function testOf() ); } - public function testDefer() - { - $loaded = false; - $set = Set::defer((static function() use (&$loaded) { - yield 1; - yield 2; - yield 3; - $loaded = true; - })()); - - $this->assertInstanceOf(Set::class, $set); - $this->assertFalse($loaded); - $this->assertSame([1, 2, 3], $set->toList()); - $this->assertTrue($loaded); - } - public function testLazy() { $loaded = false; @@ -112,8 +91,7 @@ public function testAdd() $s = Set::of()->add(42); $this->assertSame(1, $s->size()); - $this->assertSame(1, $s->count()); - $s->add(24); + $_ = $s->add(24); $this->assertSame(1, $s->size()); $s = $s->add(24); $this->assertInstanceOf(Set::class, $s); @@ -253,7 +231,7 @@ public function testForeach() ->add(4); $count = 0; - $s->foreach(function(int $v) use (&$count) { + $_ = $s->foreach(function(int $v) use (&$count) { $this->assertSame(++$count, $v); }); $this->assertSame(4, $count); @@ -316,12 +294,14 @@ public function testFlatMap() $this->assertTrue($loaded); $loaded = false; - $set = Set::defer((static function() use (&$loaded) { + $set = Set::lazy(static function() use (&$loaded) { yield 1; yield 2; yield 3; $loaded = true; - })())->flatMap(static fn($i) => Set::of($i, $i + 1)); + }) + ->snap() + ->flatMap(static fn($i) => Set::of($i, $i + 1)); $this->assertInstanceOf(Set::class, $set); $this->assertFalse($loaded); @@ -337,16 +317,12 @@ public function testPartition() ->add(3) ->add(4); - $s2 = $s->partition(static function(int $v) { + [$true, $false] = $s->partition(static function(int $v) { return $v % 2 === 0; }); - $this->assertNotSame($s, $s2); - $this->assertInstanceOf(Map::class, $s2); $this->assertSame([1, 2, 3, 4], $s->toList()); - $this->assertInstanceOf(Set::class, $this->get($s2, true)); - $this->assertInstanceOf(Set::class, $this->get($s2, false)); - $this->assertSame([2, 4], $this->get($s2, true)->toList()); - $this->assertSame([1, 3], $this->get($s2, false)->toList()); + $this->assertSame([2, 4], $true->toList()); + $this->assertSame([1, 3], $false->toList()); } public function testSort() @@ -521,7 +497,7 @@ public function testPossibilityToCleanupResourcesWhenGeneratorStoppedBeforeEnd() static fn() => null, ); - $this->assertSame('public function testInterface()', $line); + $this->assertSame('public function testOf()', $line); $this->assertSame(1, $started); $this->assertTrue($cleanupCalled); $this->assertFalse($endReached); @@ -581,7 +557,7 @@ public function testSafeguard() $stop = new \Exception; try { - Set::of(new \ArrayObject([1]), new \ArrayObject([2]), new \ArrayObject([3]), new \ArrayObject([1]))->safeguard( + $_ = Set::of(new \ArrayObject([1]), new \ArrayObject([2]), new \ArrayObject([3]), new \ArrayObject([1]))->safeguard( Set::of(), static fn($unique, $value) => match ($unique->contains($value[0])) { true => throw $stop, @@ -595,21 +571,23 @@ public function testSafeguard() try { $loaded = false; - $set = Set::defer((static function() use (&$loaded) { + $set = Set::lazy(static function() use (&$loaded) { $loaded = true; yield new \ArrayObject([1]); yield new \ArrayObject([2]); yield new \ArrayObject([3]); yield new \ArrayObject([1]); - })())->safeguard( - Set::of(), - static fn($unique, $value) => match ($unique->contains($value[0])) { - true => throw $stop, - false => ($unique)($value[0]), - }, - ); + }) + ->snap() + ->safeguard( + Set::of(), + static fn($unique, $value) => match ($unique->contains($value[0])) { + true => throw $stop, + false => ($unique)($value[0]), + }, + ); $this->assertFalse($loaded); - $set->toList(); + $_ = $set->toList(); $this->fail('it should throw'); } catch (\Exception $e) { $this->assertSame($stop, $e); @@ -631,7 +609,7 @@ public function testSafeguard() }, ); $this->assertFalse($loaded); - $set->toList(); + $_ = $set->toList(); $this->fail('it should throw'); } catch (\Exception $e) { $this->assertSame($stop, $e); @@ -648,13 +626,13 @@ public function testMemoize() $this->assertSame([1, 2, 3, 4], $set->memoize()->toList()); $loaded = false; - $set = Set::defer((static function() use (&$loaded) { + $set = Set::lazy(static function() use (&$loaded) { yield 1; yield 2; yield 3; yield 4; $loaded = true; - })()); + })->snap(); $this->assertFalse($loaded); $memoized = $set->memoize(); $this->assertTrue($loaded); diff --git a/tests/StateTest.php b/tests/StateTest.php deleted file mode 100644 index 0e07e9e2..00000000 --- a/tests/StateTest.php +++ /dev/null @@ -1,74 +0,0 @@ -forAll( - Set::type(), - Set::type(), - Set::type(), - ) - ->prove(function($state, $initialValue, $newValue) { - $monad = State::of(static fn($state) => Result::of($state, $initialValue)); - $monad2 = $monad->map(function($value) use ($initialValue, $newValue) { - $this->assertSame($initialValue, $value); - - return $newValue; - }); - - $this->assertInstanceOf(Result::class, $monad->run($state)); - $this->assertInstanceOf(Result::class, $monad2->run($state)); - $this->assertSame($state, $monad->run($state)->state()); - $this->assertSame($state, $monad2->run($state)->state()); - $this->assertSame($initialValue, $monad->run($state)->value()); - $this->assertSame($newValue, $monad2->run($state)->value()); - }); - } - - public function testFlatMap(): BlackBox\Proof - { - return $this - ->forAll( - Set::type(), - Set::type(), - Set::type(), - Set::type(), - ) - ->prove(function($initialState, $newState, $initialValue, $newValue) { - $monad = State::of(static fn($state) => Result::of($state, $initialValue)); - $monad2 = $monad->flatMap(function($value) use ($initialValue, $initialState, $newState, $newValue) { - $this->assertSame($initialValue, $value); - - return State::of(function($state) use ($initialState, $newState, $newValue) { - $this->assertSame($initialState, $state); - - return Result::of($newState, $newValue); - }); - }); - - $this->assertInstanceOf(Result::class, $monad->run($initialState)); - $this->assertInstanceOf(Result::class, $monad2->run($initialState)); - $this->assertSame($initialState, $monad->run($initialState)->state()); - $this->assertSame($newState, $monad2->run($initialState)->state()); - $this->assertSame($initialValue, $monad->run($initialState)->value()); - $this->assertSame($newValue, $monad2->run($initialState)->value()); - }); - } -} diff --git a/tests/StrTest.php b/tests/StrTest.php index 3a79c921..fb538511 100644 --- a/tests/StrTest.php +++ b/tests/StrTest.php @@ -53,7 +53,7 @@ public function testSplit() $sequence = $str->split(); $this->assertInstanceOf(Sequence::class, $sequence); - $this->assertCount(3, $sequence); + $this->assertSame(3, $sequence->size()); foreach ($sequence->toList() as $part) { $this->assertInstanceOf(S::class, $part); @@ -75,7 +75,7 @@ public function testSplit() $sequence = $str->split(''); $this->assertInstanceOf(Sequence::class, $sequence); - $this->assertCount(3, $sequence); + $this->assertSame(3, $sequence->size()); foreach ($sequence->toList() as $part) { $this->assertInstanceOf(S::class, $part); @@ -88,7 +88,7 @@ public function testSplit() $str = S::of('f|o|o'); $sequence = $str->split('|'); $this->assertInstanceOf(Sequence::class, $sequence); - $this->assertCount(3, $sequence); + $this->assertSame(3, $sequence->size()); foreach ($sequence->toList() as $part) { $this->assertInstanceOf(S::class, $part); @@ -103,7 +103,7 @@ public function testSplitOnZeroString() { $parts = S::of('10101')->split('0'); - $this->assertCount(3, $parts); + $this->assertSame(3, $parts->size()); $this->assertSame('1', $this->get($parts, 0)->toString()); $this->assertSame('1', $this->get($parts, 1)->toString()); $this->assertSame('1', $this->get($parts, 2)->toString()); @@ -516,13 +516,13 @@ public function testCapture() $map = $str->capture('@^(?:http://)?(?P[^/]+)@i'); $this->assertInstanceOf(Map::class, $map); - $this->assertCount(3, $map); + $this->assertSame(3, $map->size()); $this->assertSame('http://www.php.net', $this->get($map, 0)->toString()); $this->assertSame('www.php.net', $this->get($map, 1)->toString()); $this->assertSame('www.php.net', $this->get($map, 'host')->toString()); $map = $str->capture(S::of('@^(?:http://)?(?P[^/]+)@i')); - $this->assertCount(3, $map); + $this->assertSame(3, $map->size()); } public function testCastNullValuesWhenCapturing() @@ -531,7 +531,7 @@ public function testCastNullValuesWhenCapturing() $matches = $str->capture('~(?([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*|\*))(; ?q=(?\d+(\.\d+)?))?~'); $this->assertInstanceOf(Map::class, $matches); - $this->assertCount(9, $matches); + $this->assertSame(9, $matches->size()); $this->assertSame('en;q=0.7', $this->get($matches, 0)->toString()); $this->assertSame('en', $this->get($matches, 1)->toString()); $this->assertSame('en', $this->get($matches, 2)->toString());