Skip to content

Commit 909b958

Browse files
authored
Merge pull request #20 from Innmind/identity-monad
Add `Identity` monad
2 parents 6ead492 + a0b90f6 commit 909b958

File tree

9 files changed

+262
-1
lines changed

9 files changed

+262
-1
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## [Unreleased]
4+
5+
### Added
6+
7+
- `Innmind\Immutable\Identity`
8+
- `Innmind\Immutable\Sequence::toIdentity()`
9+
310
## 5.5.0 - 2024-06-02
411

512
### Changed

docs/structures/identity.md

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# `Identity`
2+
3+
This is the simplest monad there is. It's a simple wrapper around a value to allow chaining calls on this value.
4+
5+
Let's say you have a string you want to camelize, here's how you'd do it:
6+
7+
=== "Identity"
8+
```php
9+
$value = Identity::of('some value to camelize')
10+
->map(fn($string) => \explode(' ', $string))
11+
->map(fn($parts) => \array_map(
12+
\ucfirst(...),
13+
$parts,
14+
))
15+
->map(fn($parts) => \implode('', $parts))
16+
->map(\lcfirst(...))
17+
->unwrap();
18+
19+
echo $value; // outputs "someValueToCamelize"
20+
```
21+
22+
=== "Imperative"
23+
```php
24+
$string = 'some value to camelize';
25+
$parts = \explode(' ', $string);
26+
$parts = \array_map(
27+
\ucfirst(...),
28+
$parts,
29+
);
30+
$string = \implode('', $parts);
31+
$value = \lcfirst($string);
32+
33+
echo $value; // outputs "someValueToCamelize"
34+
```
35+
36+
=== "Pyramid of doom"
37+
```php
38+
$value = \lcfirst(
39+
\implode(
40+
'',
41+
\array_map(
42+
\ucfirst(...),
43+
\explode(
44+
' ',
45+
'some value to camelize',
46+
),
47+
),
48+
),
49+
);
50+
51+
echo $value; // outputs "someValueToCamelize"
52+
```
53+
54+
!!! abstract ""
55+
In the end this monad does not provide any behaviour, it's a different way to write and read your code.

docs/structures/index.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Structures
22

3-
This library provides the 10 following structures:
3+
This library provides the following structures:
44

55
- [`Sequence`](sequence.md)
66
- [`Set`](set.md)
@@ -10,6 +10,7 @@ This library provides the 10 following structures:
1010
- [`Maybe`](maybe.md)
1111
- [`Either`](either.md)
1212
- [`Validation`](validation.md)
13+
- [`Identity`](identity.md)
1314
- [`State`](state.md)
1415
- [`Fold`](fold.md)
1516

docs/structures/sequence.md

+28
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,34 @@ $sequence = Sequence::ints(1, 2, 3, 4);
572572
$sequence->reverse()->equals(Sequence::ints(4, 3, 2, 1));
573573
```
574574

575+
### `->toIdentity()`
576+
577+
This method wraps the sequence in an [`Identity` monad](identity.md).
578+
579+
Let's say you have a sequence of strings representing the parts of a file and you want to build a file object:
580+
581+
=== "Do"
582+
```php
583+
$file = Sequence::of('a', 'b', 'c', 'etc...')
584+
->toIdentity()
585+
->map(Content::ofChunks(...))
586+
->map(static fn($content) => File::named('foo', $content))
587+
->unwrap();
588+
```
589+
590+
=== "Instead of..."
591+
```php
592+
$file = File::named(
593+
'foo',
594+
Content::ofChunks(
595+
Sequence::of('a', 'b', 'c', 'etc...'),
596+
),
597+
);
598+
```
599+
600+
??? note
601+
Here `Content` and `File` are imaginary classes, but you can find equivalent classes in [`innmind/filesystem`](https://packagist.org/packages/innmind/filesystem).
602+
575603
### `->safeguard()`
576604

577605
This method allows you to make sure all values conforms to an assertion before continuing using the sequence.

mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ nav:
1414
- Maybe: structures/maybe.md
1515
- Either: structures/either.md
1616
- Validation: structures/validation.md
17+
- Identity: structures/identity.md
1718
- State: structures/state.md
1819
- Fold: structures/fold.md
1920
- Monoids: MONOIDS.md

proofs/identity.php

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
use Innmind\Immutable\Identity;
5+
use Innmind\BlackBox\Set;
6+
7+
return static function() {
8+
yield proof(
9+
'Identity::unwrap()',
10+
given(Set\Type::any()),
11+
static fn($assert, $value) => $assert->same(
12+
$value,
13+
Identity::of($value)->unwrap(),
14+
),
15+
);
16+
17+
yield proof(
18+
'Identity::map()',
19+
given(
20+
Set\Type::any(),
21+
Set\Type::any(),
22+
),
23+
static fn($assert, $initial, $expected) => $assert->same(
24+
$expected,
25+
Identity::of($initial)
26+
->map(static function($value) use ($assert, $initial, $expected) {
27+
$assert->same($initial, $value);
28+
29+
return $expected;
30+
})
31+
->unwrap(),
32+
),
33+
);
34+
35+
yield proof(
36+
'Identity::flatMap()',
37+
given(
38+
Set\Type::any(),
39+
Set\Type::any(),
40+
),
41+
static fn($assert, $initial, $expected) => $assert->same(
42+
$expected,
43+
Identity::of($initial)
44+
->flatMap(static function($value) use ($assert, $initial, $expected) {
45+
$assert->same($initial, $value);
46+
47+
return Identity::of($expected);
48+
})
49+
->unwrap(),
50+
),
51+
);
52+
53+
yield proof(
54+
'Identity::map() and ::flatMap() interchangeability',
55+
given(
56+
Set\Type::any(),
57+
Set\Type::any(),
58+
Set\Type::any(),
59+
),
60+
static fn($assert, $initial, $intermediate, $expected) => $assert->same(
61+
Identity::of($initial)
62+
->flatMap(static fn() => Identity::of($intermediate))
63+
->map(static fn() => $expected)
64+
->unwrap(),
65+
Identity::of($initial)
66+
->flatMap(
67+
static fn() => Identity::of($intermediate)->map(
68+
static fn() => $expected,
69+
),
70+
)
71+
->unwrap(),
72+
),
73+
);
74+
};

proofs/sequence.php

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
use Innmind\Immutable\Sequence;
5+
6+
return static function() {
7+
yield test(
8+
'Sequence::toIdentity()',
9+
static function($assert) {
10+
$sequence = Sequence::of();
11+
12+
$assert->same(
13+
$sequence,
14+
$sequence->toIdentity()->unwrap(),
15+
);
16+
},
17+
);
18+
};

src/Identity.php

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<?php
2+
declare(strict_types = 1);
3+
4+
namespace Innmind\Immutable;
5+
6+
/**
7+
* @psalm-immutable
8+
* @template T
9+
*/
10+
final class Identity
11+
{
12+
/** @var T */
13+
private mixed $value;
14+
15+
/**
16+
* @param T $value
17+
*/
18+
private function __construct(mixed $value)
19+
{
20+
$this->value = $value;
21+
}
22+
23+
/**
24+
* @psalm-pure
25+
* @template A
26+
*
27+
* @param A $value
28+
*
29+
* @return self<A>
30+
*/
31+
public static function of(mixed $value): self
32+
{
33+
return new self($value);
34+
}
35+
36+
/**
37+
* @template U
38+
*
39+
* @param callable(T): U $map
40+
*
41+
* @return self<U>
42+
*/
43+
public function map(callable $map): self
44+
{
45+
/** @psalm-suppress ImpureFunctionCall */
46+
return new self($map($this->value));
47+
}
48+
49+
/**
50+
* @template U
51+
*
52+
* @param callable(T): self<U> $map
53+
*
54+
* @return self<U>
55+
*/
56+
public function flatMap(callable $map): self
57+
{
58+
/** @psalm-suppress ImpureFunctionCall */
59+
return $map($this->value);
60+
}
61+
62+
/**
63+
* @return T
64+
*/
65+
public function unwrap(): mixed
66+
{
67+
return $this->value;
68+
}
69+
}

src/Sequence.php

+8
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,14 @@ public function toSet(): Set
591591
return $this->implementation->toSet();
592592
}
593593

594+
/**
595+
* @return Identity<self<T>>
596+
*/
597+
public function toIdentity(): Identity
598+
{
599+
return Identity::of($this);
600+
}
601+
594602
/**
595603
* @return list<T>
596604
*/

0 commit comments

Comments
 (0)