Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
Baptouuuu committed Dec 11, 2022
2 parents 30aadb0 + 7187b3c commit 8de9051
Show file tree
Hide file tree
Showing 17 changed files with 399 additions and 22 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Changelog

## 4.8.0 - 2022-12-11

### Added

- `Innmind\Immutable\Sequence::safeguard`
- `Innmind\Immutable\Set::safeguard`

### Fixed

- `Innmind\Immutable\Set::remove()` no longer unwraps deferred and lazy `Set`s
- Fix calling unnecessary methods for some `Set` operations

## 4.7.1 - 2022-11-27

### Fixed
Expand Down
33 changes: 33 additions & 0 deletions docs/SEQUENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -500,3 +500,36 @@ $pairs = $firnames
->toList();
$pairs; // [['John', 'Doe'], ['Luke', 'Skywalker'], ['James', 'Kirk']]
```

## `->safeguard()`

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

```php
$uniqueFiles = Sequence::of('a', 'b', 'c', 'a')
->safeguard(
Set::strings()
static fn(Set $names, string $name) => match ($names->contains($name)) {
true => throw new \LogicException("$name is already used"),
false => $names->add($name),
},
);
```

This example will throw because there is the value `a` twice.

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).

```php
Sequence::lazyStartingWith('a', 'b', 'c', 'a')
->safeguard(
Set::strings()
static fn(Set $names, string $name) => match ($names->contains($name)) {
true => throw new \LogicException("$name is already used"),
false => $names->add($name),
},
)
->foreach(static fn($name) => print($name));
```

This example will print `a`, `b` and `c` before throwing an exception because of the second `a`. Use this method carefully.
43 changes: 43 additions & 0 deletions docs/SET.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,46 @@ Set::ints(1, 3, 5, 7)->any($isOdd); // true
Set::ints(1, 3, 4, 5, 7)->any($isOdd); // true
Set::ints(2, 4, 6, 8)->any($isOdd); // false
```

## `->safeguard()`

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

```php
$uniqueFiles = Set::of(
new \ArrayObject(['name' => 'a']),
new \ArrayObject(['name' => 'b']),
new \ArrayObject(['name' => 'c']),
new \ArrayObject(['name' => 'a']),
)
->safeguard(
Set::strings()
static fn(Set $names, string $value) => match ($names->contains($value['name'])) {
true => throw new \LogicException("{$value['name']} is already used"),
false => $names->add($value['name']),
},
);
```

This example will throw because there is the value `a` twice.

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).

```php
Set::lazy(function() {
yield new \ArrayObject(['name' => 'a']);
yield new \ArrayObject(['name' => 'b']);
yield new \ArrayObject(['name' => 'c']);
yield new \ArrayObject(['name' => 'a']);
})
->safeguard(
Set::strings()
static fn(Set $names, string $value) => match ($names->contains($value['name'])) {
true => throw new \LogicException("{$value['name']} is already used"),
false => $names->add($value['name']),
},
)
->foreach(static fn($value) => print($value['name']));
```

This example will print `a`, `b` and `c` before throwing an exception because of the second `a`. Use this method carefully.
20 changes: 20 additions & 0 deletions src/Sequence.php
Original file line number Diff line number Diff line change
Expand Up @@ -669,4 +669,24 @@ public function zip(self $sequence): self
{
return new self($this->implementation->zip($sequence->implementation));
}

/**
* Make sure every value conforms to the assertion, you must throw an
* exception when a value does not conform.
*
* For deferred and lazy sequences the assertion is called on the go,
* meaning subsequent operations may start before reaching a value that
* doesn't conform. To be used carefully.
*
* @template R
*
* @param R $carry
* @param callable(R, T): R $assert
*
* @return self<T>
*/
public function safeguard($carry, callable $assert)
{
return new self($this->implementation->safeguard($carry, $assert));
}
}
23 changes: 23 additions & 0 deletions src/Sequence/Defer.php
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,29 @@ public function zip(Implementation $sequence): Implementation
);
}

/**
* @template R
* @param R $carry
* @param callable(R, T): R $assert
*
* @return self<T>
*/
public function safeguard($carry, callable $assert): self
{
/** @psalm-suppress ImpureFunctionCall */
return new self(
(static function(\Iterator $values, mixed $carry, callable $assert): \Generator {
/** @var T $value */
foreach ($values as $value) {
/** @var R */
$carry = $assert($carry, $value);

yield $value;
}
})($this->values, $carry, $assert),
);
}

/**
* @return Implementation<T>
*/
Expand Down
11 changes: 11 additions & 0 deletions src/Sequence/Implementation.php
Original file line number Diff line number Diff line change
Expand Up @@ -310,4 +310,15 @@ public function match(callable $wrap, callable $match, callable $empty);
* @return self<array{T, S}>
*/
public function zip(self $sequence): self;

/**
* Make sure every value conforms to the assertion
*
* @template R
* @param R $carry
* @param callable(R, T): R $assert
*
* @return self<T>
*/
public function safeguard($carry, callable $assert): self;
}
22 changes: 22 additions & 0 deletions src/Sequence/Lazy.php
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,28 @@ static function(callable $registerCleanup) use ($values, $sequence) {
);
}

/**
* @template R
* @param R $carry
* @param callable(R, T): R $assert
*
* @return self<T>
*/
public function safeguard($carry, callable $assert): self
{
$values = $this->values;

return new self(
static function(callable $registerCleanup) use ($values, $carry, $assert): \Generator {
foreach ($values($registerCleanup) as $value) {
$carry = $assert($carry, $value);

yield $value;
}
},
);
}

/**
* @return Implementation<T>
*/
Expand Down
14 changes: 14 additions & 0 deletions src/Sequence/Primitive.php
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,20 @@ public function zip(Implementation $sequence): Implementation
return new self($values);
}

/**
* @template R
* @param R $carry
* @param callable(R, T): R $assert
*
* @return self<T>
*/
public function safeguard($carry, callable $assert): self
{
$_ = $this->reduce($carry, $assert);

return $this;
}

private function has(int $index): bool
{
return \array_key_exists($index, $this->values);
Expand Down
20 changes: 20 additions & 0 deletions src/Set.php
Original file line number Diff line number Diff line change
Expand Up @@ -462,4 +462,24 @@ static function(array $carry, $value): array {
},
);
}

/**
* Make sure every value conforms to the assertion, you must throw an
* exception when a value does not conform.
*
* For deferred and lazy sets the assertion is called on the go, meaning
* subsequent operations may start before reaching a value that doesn't
* conform. To be used carefully.
*
* @template R
*
* @param R $carry
* @param callable(R, T): R $assert
*
* @return self<T>
*/
public function safeguard($carry, callable $assert)
{
return new self($this->implementation->safeguard($carry, $assert));
}
}
34 changes: 25 additions & 9 deletions src/Set/Defer.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ final class Defer implements Implementation
*/
public function __construct(Sequence\Implementation $values)
{
$this->values = $values->distinct();
$this->values = $values;
}

/**
Expand All @@ -36,7 +36,7 @@ public function __construct(Sequence\Implementation $values)
*/
public function __invoke($element): self
{
return new self(($this->values)($element));
return self::distinct(($this->values)($element));
}

/**
Expand All @@ -49,7 +49,7 @@ public function __invoke($element): self
*/
public static function of(\Generator $generator): self
{
return new self(new Sequence\Defer($generator));
return self::distinct(new Sequence\Defer($generator));
}

public function size(): int
Expand Down Expand Up @@ -101,10 +101,6 @@ public function contains($element): bool
*/
public function remove($element): self
{
if (!$this->contains($element)) {
return $this;
}

return new self($this->values->filter(
static fn($value) => $value !== $element,
));
Expand Down Expand Up @@ -174,7 +170,7 @@ public function groupBy(callable $discriminator): Map
*/
public function map(callable $function): self
{
return new self($this->values->map($function));
return self::distinct($this->values->map($function));
}

/**
Expand Down Expand Up @@ -210,7 +206,7 @@ public function sort(callable $function): Sequence
*/
public function merge(Implementation $set): self
{
return new self($this->values->append($set->sequence()));
return self::distinct($this->values->append($set->sequence()));
}

/**
Expand Down Expand Up @@ -243,11 +239,31 @@ public function find(callable $predicate): Maybe
return $this->values->find($predicate);
}

/**
* @template R
* @param R $carry
* @param callable(R, T): R $assert
*
* @return self<T>
*/
public function safeguard($carry, callable $assert): self
{
return new self($this->values->safeguard($carry, $assert));
}

/**
* @return Sequence\Implementation<T>
*/
public function sequence(): Sequence\Implementation
{
return $this->values;
}

/**
* @psalm-pure
*/
private static function distinct(Sequence\Implementation $values): self
{
return new self($values->distinct());
}
}
11 changes: 11 additions & 0 deletions src/Set/Implementation.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,17 @@ public function empty(): bool;
*/
public function find(callable $predicate): Maybe;

/**
* Make sure every value conforms to the assertion
*
* @template R
* @param R $carry
* @param callable(R, T): R $assert
*
* @return self<T>
*/
public function safeguard($carry, callable $assert): self;

/**
* @return Sequence\Implementation<T>
*/
Expand Down
Loading

0 comments on commit 8de9051

Please sign in to comment.