Skip to content

Commit 8de9051

Browse files
committed
Merge branch 'develop'
* develop: specify next release fix calling ->distinct() to many times do not unwrap the whole set to remove an element add Set::safeguard fix test add Sequence::safeguard
2 parents 30aadb0 + 7187b3c commit 8de9051

17 files changed

+399
-22
lines changed

CHANGELOG.md

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

3+
## 4.8.0 - 2022-12-11
4+
5+
### Added
6+
7+
- `Innmind\Immutable\Sequence::safeguard`
8+
- `Innmind\Immutable\Set::safeguard`
9+
10+
### Fixed
11+
12+
- `Innmind\Immutable\Set::remove()` no longer unwraps deferred and lazy `Set`s
13+
- Fix calling unnecessary methods for some `Set` operations
14+
315
## 4.7.1 - 2022-11-27
416

517
### Fixed

docs/SEQUENCE.md

+33
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,36 @@ $pairs = $firnames
500500
->toList();
501501
$pairs; // [['John', 'Doe'], ['Luke', 'Skywalker'], ['James', 'Kirk']]
502502
```
503+
504+
## `->safeguard()`
505+
506+
This method allows you to make sure all values conforms to an assertion before continuing using the sequence.
507+
508+
```php
509+
$uniqueFiles = Sequence::of('a', 'b', 'c', 'a')
510+
->safeguard(
511+
Set::strings()
512+
static fn(Set $names, string $name) => match ($names->contains($name)) {
513+
true => throw new \LogicException("$name is already used"),
514+
false => $names->add($name),
515+
},
516+
);
517+
```
518+
519+
This example will throw because there is the value `a` twice.
520+
521+
This method is especially useful for deferred or lazy sequences because it allows to make sure all values conforms after this call whithout unwrapping the whole sequence first. The downside of this lazy evaluation is that some operations may start before reaching a non conforming value (example below).
522+
523+
```php
524+
Sequence::lazyStartingWith('a', 'b', 'c', 'a')
525+
->safeguard(
526+
Set::strings()
527+
static fn(Set $names, string $name) => match ($names->contains($name)) {
528+
true => throw new \LogicException("$name is already used"),
529+
false => $names->add($name),
530+
},
531+
)
532+
->foreach(static fn($name) => print($name));
533+
```
534+
535+
This example will print `a`, `b` and `c` before throwing an exception because of the second `a`. Use this method carefully.

docs/SET.md

+43
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,46 @@ Set::ints(1, 3, 5, 7)->any($isOdd); // true
339339
Set::ints(1, 3, 4, 5, 7)->any($isOdd); // true
340340
Set::ints(2, 4, 6, 8)->any($isOdd); // false
341341
```
342+
343+
## `->safeguard()`
344+
345+
This method allows you to make sure all values conforms to an assertion before continuing using the set.
346+
347+
```php
348+
$uniqueFiles = Set::of(
349+
new \ArrayObject(['name' => 'a']),
350+
new \ArrayObject(['name' => 'b']),
351+
new \ArrayObject(['name' => 'c']),
352+
new \ArrayObject(['name' => 'a']),
353+
)
354+
->safeguard(
355+
Set::strings()
356+
static fn(Set $names, string $value) => match ($names->contains($value['name'])) {
357+
true => throw new \LogicException("{$value['name']} is already used"),
358+
false => $names->add($value['name']),
359+
},
360+
);
361+
```
362+
363+
This example will throw because there is the value `a` twice.
364+
365+
This method is especially useful for deferred or lazy sets because it allows to make sure all values conforms after this call whithout unwrapping the whole set first. The downside of this lazy evaluation is that some operations may start before reaching a non conforming value (example below).
366+
367+
```php
368+
Set::lazy(function() {
369+
yield new \ArrayObject(['name' => 'a']);
370+
yield new \ArrayObject(['name' => 'b']);
371+
yield new \ArrayObject(['name' => 'c']);
372+
yield new \ArrayObject(['name' => 'a']);
373+
})
374+
->safeguard(
375+
Set::strings()
376+
static fn(Set $names, string $value) => match ($names->contains($value['name'])) {
377+
true => throw new \LogicException("{$value['name']} is already used"),
378+
false => $names->add($value['name']),
379+
},
380+
)
381+
->foreach(static fn($value) => print($value['name']));
382+
```
383+
384+
This example will print `a`, `b` and `c` before throwing an exception because of the second `a`. Use this method carefully.

src/Sequence.php

+20
Original file line numberDiff line numberDiff line change
@@ -669,4 +669,24 @@ public function zip(self $sequence): self
669669
{
670670
return new self($this->implementation->zip($sequence->implementation));
671671
}
672+
673+
/**
674+
* Make sure every value conforms to the assertion, you must throw an
675+
* exception when a value does not conform.
676+
*
677+
* For deferred and lazy sequences the assertion is called on the go,
678+
* meaning subsequent operations may start before reaching a value that
679+
* doesn't conform. To be used carefully.
680+
*
681+
* @template R
682+
*
683+
* @param R $carry
684+
* @param callable(R, T): R $assert
685+
*
686+
* @return self<T>
687+
*/
688+
public function safeguard($carry, callable $assert)
689+
{
690+
return new self($this->implementation->safeguard($carry, $assert));
691+
}
672692
}

src/Sequence/Defer.php

+23
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,29 @@ public function zip(Implementation $sequence): Implementation
634634
);
635635
}
636636

637+
/**
638+
* @template R
639+
* @param R $carry
640+
* @param callable(R, T): R $assert
641+
*
642+
* @return self<T>
643+
*/
644+
public function safeguard($carry, callable $assert): self
645+
{
646+
/** @psalm-suppress ImpureFunctionCall */
647+
return new self(
648+
(static function(\Iterator $values, mixed $carry, callable $assert): \Generator {
649+
/** @var T $value */
650+
foreach ($values as $value) {
651+
/** @var R */
652+
$carry = $assert($carry, $value);
653+
654+
yield $value;
655+
}
656+
})($this->values, $carry, $assert),
657+
);
658+
}
659+
637660
/**
638661
* @return Implementation<T>
639662
*/

src/Sequence/Implementation.php

+11
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,15 @@ public function match(callable $wrap, callable $match, callable $empty);
310310
* @return self<array{T, S}>
311311
*/
312312
public function zip(self $sequence): self;
313+
314+
/**
315+
* Make sure every value conforms to the assertion
316+
*
317+
* @template R
318+
* @param R $carry
319+
* @param callable(R, T): R $assert
320+
*
321+
* @return self<T>
322+
*/
323+
public function safeguard($carry, callable $assert): self;
313324
}

src/Sequence/Lazy.php

+22
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,28 @@ static function(callable $registerCleanup) use ($values, $sequence) {
682682
);
683683
}
684684

685+
/**
686+
* @template R
687+
* @param R $carry
688+
* @param callable(R, T): R $assert
689+
*
690+
* @return self<T>
691+
*/
692+
public function safeguard($carry, callable $assert): self
693+
{
694+
$values = $this->values;
695+
696+
return new self(
697+
static function(callable $registerCleanup) use ($values, $carry, $assert): \Generator {
698+
foreach ($values($registerCleanup) as $value) {
699+
$carry = $assert($carry, $value);
700+
701+
yield $value;
702+
}
703+
},
704+
);
705+
}
706+
685707
/**
686708
* @return Implementation<T>
687709
*/

src/Sequence/Primitive.php

+14
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,20 @@ public function zip(Implementation $sequence): Implementation
485485
return new self($values);
486486
}
487487

488+
/**
489+
* @template R
490+
* @param R $carry
491+
* @param callable(R, T): R $assert
492+
*
493+
* @return self<T>
494+
*/
495+
public function safeguard($carry, callable $assert): self
496+
{
497+
$_ = $this->reduce($carry, $assert);
498+
499+
return $this;
500+
}
501+
488502
private function has(int $index): bool
489503
{
490504
return \array_key_exists($index, $this->values);

src/Set.php

+20
Original file line numberDiff line numberDiff line change
@@ -462,4 +462,24 @@ static function(array $carry, $value): array {
462462
},
463463
);
464464
}
465+
466+
/**
467+
* Make sure every value conforms to the assertion, you must throw an
468+
* exception when a value does not conform.
469+
*
470+
* For deferred and lazy sets the assertion is called on the go, meaning
471+
* subsequent operations may start before reaching a value that doesn't
472+
* conform. To be used carefully.
473+
*
474+
* @template R
475+
*
476+
* @param R $carry
477+
* @param callable(R, T): R $assert
478+
*
479+
* @return self<T>
480+
*/
481+
public function safeguard($carry, callable $assert)
482+
{
483+
return new self($this->implementation->safeguard($carry, $assert));
484+
}
465485
}

src/Set/Defer.php

+25-9
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ final class Defer implements Implementation
2626
*/
2727
public function __construct(Sequence\Implementation $values)
2828
{
29-
$this->values = $values->distinct();
29+
$this->values = $values;
3030
}
3131

3232
/**
@@ -36,7 +36,7 @@ public function __construct(Sequence\Implementation $values)
3636
*/
3737
public function __invoke($element): self
3838
{
39-
return new self(($this->values)($element));
39+
return self::distinct(($this->values)($element));
4040
}
4141

4242
/**
@@ -49,7 +49,7 @@ public function __invoke($element): self
4949
*/
5050
public static function of(\Generator $generator): self
5151
{
52-
return new self(new Sequence\Defer($generator));
52+
return self::distinct(new Sequence\Defer($generator));
5353
}
5454

5555
public function size(): int
@@ -101,10 +101,6 @@ public function contains($element): bool
101101
*/
102102
public function remove($element): self
103103
{
104-
if (!$this->contains($element)) {
105-
return $this;
106-
}
107-
108104
return new self($this->values->filter(
109105
static fn($value) => $value !== $element,
110106
));
@@ -174,7 +170,7 @@ public function groupBy(callable $discriminator): Map
174170
*/
175171
public function map(callable $function): self
176172
{
177-
return new self($this->values->map($function));
173+
return self::distinct($this->values->map($function));
178174
}
179175

180176
/**
@@ -210,7 +206,7 @@ public function sort(callable $function): Sequence
210206
*/
211207
public function merge(Implementation $set): self
212208
{
213-
return new self($this->values->append($set->sequence()));
209+
return self::distinct($this->values->append($set->sequence()));
214210
}
215211

216212
/**
@@ -243,11 +239,31 @@ public function find(callable $predicate): Maybe
243239
return $this->values->find($predicate);
244240
}
245241

242+
/**
243+
* @template R
244+
* @param R $carry
245+
* @param callable(R, T): R $assert
246+
*
247+
* @return self<T>
248+
*/
249+
public function safeguard($carry, callable $assert): self
250+
{
251+
return new self($this->values->safeguard($carry, $assert));
252+
}
253+
246254
/**
247255
* @return Sequence\Implementation<T>
248256
*/
249257
public function sequence(): Sequence\Implementation
250258
{
251259
return $this->values;
252260
}
261+
262+
/**
263+
* @psalm-pure
264+
*/
265+
private static function distinct(Sequence\Implementation $values): self
266+
{
267+
return new self($values->distinct());
268+
}
253269
}

src/Set/Implementation.php

+11
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,17 @@ public function empty(): bool;
170170
*/
171171
public function find(callable $predicate): Maybe;
172172

173+
/**
174+
* Make sure every value conforms to the assertion
175+
*
176+
* @template R
177+
* @param R $carry
178+
* @param callable(R, T): R $assert
179+
*
180+
* @return self<T>
181+
*/
182+
public function safeguard($carry, callable $assert): self;
183+
173184
/**
174185
* @return Sequence\Implementation<T>
175186
*/

0 commit comments

Comments
 (0)