diff --git a/CHANGELOG.md b/CHANGELOG.md index 56a1c91..c825823 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 5.6.0 - 2024-06-15 + +### Added + +- `Innmind\Immutable\Identity` +- `Innmind\Immutable\Sequence::toIdentity()` + ## 5.5.0 - 2024-06-02 ### Changed diff --git a/docs/structures/identity.md b/docs/structures/identity.md new file mode 100644 index 0000000..46c7b6e --- /dev/null +++ b/docs/structures/identity.md @@ -0,0 +1,55 @@ +# `Identity` + +This is the simplest monad there is. It's a simple wrapper around a value to allow chaining calls on this value. + +Let's say you have a string you want to camelize, here's how you'd do it: + +=== "Identity" + ```php + $value = Identity::of('some value to camelize') + ->map(fn($string) => \explode(' ', $string)) + ->map(fn($parts) => \array_map( + \ucfirst(...), + $parts, + )) + ->map(fn($parts) => \implode('', $parts)) + ->map(\lcfirst(...)) + ->unwrap(); + + echo $value; // outputs "someValueToCamelize" + ``` + +=== "Imperative" + ```php + $string = 'some value to camelize'; + $parts = \explode(' ', $string); + $parts = \array_map( + \ucfirst(...), + $parts, + ); + $string = \implode('', $parts); + $value = \lcfirst($string); + + echo $value; // outputs "someValueToCamelize" + ``` + +=== "Pyramid of doom" + ```php + $value = \lcfirst( + \implode( + '', + \array_map( + \ucfirst(...), + \explode( + ' ', + 'some value to camelize', + ), + ), + ), + ); + + echo $value; // outputs "someValueToCamelize" + ``` + +!!! abstract "" + In the end this monad does not provide any behaviour, it's a different way to write and read your code. diff --git a/docs/structures/index.md b/docs/structures/index.md index 6fb3e34..6cbe744 100644 --- a/docs/structures/index.md +++ b/docs/structures/index.md @@ -1,6 +1,6 @@ # Structures -This library provides the 10 following structures: +This library provides the following structures: - [`Sequence`](sequence.md) - [`Set`](set.md) @@ -10,6 +10,7 @@ This library provides the 10 following structures: - [`Maybe`](maybe.md) - [`Either`](either.md) - [`Validation`](validation.md) +- [`Identity`](identity.md) - [`State`](state.md) - [`Fold`](fold.md) diff --git a/docs/structures/sequence.md b/docs/structures/sequence.md index 8fe6b11..5c2a196 100644 --- a/docs/structures/sequence.md +++ b/docs/structures/sequence.md @@ -572,6 +572,38 @@ $sequence = Sequence::ints(1, 2, 3, 4); $sequence->reverse()->equals(Sequence::ints(4, 3, 2, 1)); ``` +### `->toSet()` + +It's like [`->distinct()`](#-distinct) except it returns a [`Set`](set.md) instead of a `Sequence`. + +### `->toIdentity()` + +This method wraps the sequence in an [`Identity` monad](identity.md). + +Let's say you have a sequence of strings representing the parts of a file and you want to build a file object: + +=== "Do" + ```php + $file = Sequence::of('a', 'b', 'c', 'etc...') + ->toIdentity() + ->map(Content::ofChunks(...)) + ->map(static fn($content) => File::named('foo', $content)) + ->unwrap(); + ``` + +=== "Instead of..." + ```php + $file = File::named( + 'foo', + Content::ofChunks( + Sequence::of('a', 'b', 'c', 'etc...'), + ), + ); + ``` + +??? note + Here `Content` and `File` are imaginary classes, but you can find equivalent classes in [`innmind/filesystem`](https://packagist.org/packages/innmind/filesystem). + ### `->safeguard()` This method allows you to make sure all values conforms to an assertion before continuing using the sequence. diff --git a/mkdocs.yml b/mkdocs.yml index 822608e..10793b5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,6 +14,7 @@ nav: - Maybe: structures/maybe.md - Either: structures/either.md - Validation: structures/validation.md + - Identity: structures/identity.md - State: structures/state.md - Fold: structures/fold.md - Monoids: MONOIDS.md diff --git a/proofs/identity.php b/proofs/identity.php new file mode 100644 index 0000000..a8c3f7c --- /dev/null +++ b/proofs/identity.php @@ -0,0 +1,74 @@ + $assert->same( + $value, + Identity::of($value)->unwrap(), + ), + ); + + yield proof( + 'Identity::map()', + given( + Set\Type::any(), + Set\Type::any(), + ), + static fn($assert, $initial, $expected) => $assert->same( + $expected, + Identity::of($initial) + ->map(static function($value) use ($assert, $initial, $expected) { + $assert->same($initial, $value); + + return $expected; + }) + ->unwrap(), + ), + ); + + yield proof( + 'Identity::flatMap()', + given( + Set\Type::any(), + Set\Type::any(), + ), + static fn($assert, $initial, $expected) => $assert->same( + $expected, + Identity::of($initial) + ->flatMap(static function($value) use ($assert, $initial, $expected) { + $assert->same($initial, $value); + + return Identity::of($expected); + }) + ->unwrap(), + ), + ); + + yield proof( + 'Identity::map() and ::flatMap() interchangeability', + given( + Set\Type::any(), + Set\Type::any(), + Set\Type::any(), + ), + static fn($assert, $initial, $intermediate, $expected) => $assert->same( + Identity::of($initial) + ->flatMap(static fn() => Identity::of($intermediate)) + ->map(static fn() => $expected) + ->unwrap(), + Identity::of($initial) + ->flatMap( + static fn() => Identity::of($intermediate)->map( + static fn() => $expected, + ), + ) + ->unwrap(), + ), + ); +}; diff --git a/proofs/sequence.php b/proofs/sequence.php new file mode 100644 index 0000000..4cb6b94 --- /dev/null +++ b/proofs/sequence.php @@ -0,0 +1,18 @@ +same( + $sequence, + $sequence->toIdentity()->unwrap(), + ); + }, + ); +}; diff --git a/src/Identity.php b/src/Identity.php new file mode 100644 index 0000000..9e08d90 --- /dev/null +++ b/src/Identity.php @@ -0,0 +1,69 @@ +value = $value; + } + + /** + * @psalm-pure + * @template A + * + * @param A $value + * + * @return self + */ + public static function of(mixed $value): self + { + return new self($value); + } + + /** + * @template U + * + * @param callable(T): U $map + * + * @return self + */ + public function map(callable $map): self + { + /** @psalm-suppress ImpureFunctionCall */ + return new self($map($this->value)); + } + + /** + * @template U + * + * @param callable(T): self $map + * + * @return self + */ + public function flatMap(callable $map): self + { + /** @psalm-suppress ImpureFunctionCall */ + return $map($this->value); + } + + /** + * @return T + */ + public function unwrap(): mixed + { + return $this->value; + } +} diff --git a/src/Sequence.php b/src/Sequence.php index 4e1040c..55ff561 100644 --- a/src/Sequence.php +++ b/src/Sequence.php @@ -591,6 +591,14 @@ public function toSet(): Set return $this->implementation->toSet(); } + /** + * @return Identity> + */ + public function toIdentity(): Identity + { + return Identity::of($this); + } + /** * @return list */