Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
* develop: (26 commits)
  specify next release
  increase the scenarii per proof when coverage enable to better cover conditions on generated types
  Revert "increase the number of scenarii per proof in the CI"
  increase the number of scenarii per proof in the CI
  add workflow to automatically create a release
  fix deprecations
  move deprecations messages to the top for better visibility
  deprecate the Fold monad
  deprecate the State monad
  move Sequence\Defer::load() to ::memoize()
  update changelog
  make sure via a stack trace that values are accumulated only once when intermediary sequences are not used
  do not accumulate values inside deferred sequences that nobody references
  remove the need to store generator keys
  add proofs that deferred identity/maybe holds intermediary values
  update changelog
  avoid capturing $this for a deferred either
  avoid capturing $this for a deferred maybe
  avoid capturing $this for a deferred identity
  fix deferred Identity::flatMap being lazy and not deferred
  ...
  • Loading branch information
Baptouuuu committed Nov 9, 2024
2 parents ee975c5 + b1cc6af commit 6bef242
Show file tree
Hide file tree
Showing 37 changed files with 733 additions and 147 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
extensions: mbstring, intl
coverage: none
- name: Composer
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: ${{ matrix.dependencies }}
- name: BlackBox
Expand All @@ -46,15 +46,15 @@ jobs:
extensions: mbstring, intl
coverage: xdebug
- name: Composer
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
with:
dependency-versions: ${{ matrix.dependencies }}
- name: BlackBox
run: php blackbox.php
env:
ENABLE_COVERAGE: 'true'
BLACKBOX_SET_SIZE: 1
- uses: codecov/codecov-action@v3
- uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
psalm:
Expand All @@ -72,7 +72,7 @@ jobs:
php-version: ${{ matrix.php-version }}
extensions: mbstring, intl
- name: Composer
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
- name: Psalm
run: vendor/bin/psalm --shepherd
cs:
Expand All @@ -90,6 +90,6 @@ jobs:
php-version: ${{ matrix.php-version }}
extensions: mbstring, intl
- name: Composer
uses: "ramsey/composer-install@v2"
uses: "ramsey/composer-install@v3"
- name: CS
run: vendor/bin/php-cs-fixer fix --diff --dry-run
23 changes: 23 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Create release

on:
push:
tags:
- '*'

permissions:
contents: write

jobs:
release:
name: Create release
runs-on: ubuntu-22.04
steps:
- name: Create release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}
run: |
gh release create "$tag" \
--repo="$GITHUB_REPOSITORY" \
--generate-notes
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 5.10.0 - 2024-11-09

### Added

- `Innmind\Immutable\Map::toSequence()`

### Changed

- Use `static` closures as much as possible to reduce the probability of creating circular references by capturing `$this` as it can lead to memory root buffer exhaustion.
- Remove keeping intermediary values of a deferred `Sequence` that is referenced by no one.

### Deprecated

- `Innmind\Immutable\State`
- `Innmind\Immutable\Fold`

### Fixed

- Using `string`s or `int`s as a `Map` key type and then adding keys of different types was throwing an error.

## 5.9.0 - 2024-07-05

### Added
Expand Down
2 changes: 1 addition & 1 deletion blackbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
->dumpTo('coverage.clover')
->enableWhen(true),
)
->scenariiPerProof(1),
->scenariiPerProof(50),
)
->tryToProve(Load::everythingIn(__DIR__.'/proofs/'))
->exit();
3 changes: 3 additions & 0 deletions docs/structures/fold.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# `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:
Expand Down
14 changes: 14 additions & 0 deletions docs/structures/map.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,17 @@ Return an empty new map of the same type. Useful to avoid to respecify the templ
$map = Map::of([1, 2], [3, 4]);
$map->clear()->size(); // 0
```

### `->toSequence()`

Returns an unsorted `Sequence` with all value pairs.

```php
$map = Map::of([1, 2], [3, 4]);
$map
->toSequence()
->equals(Sequence::of(
new Pair(1, 2),
new Pair(3, 4),
)); // true
```
3 changes: 3 additions & 0 deletions docs/structures/state.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# `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.
Expand Down
33 changes: 33 additions & 0 deletions proofs/identity.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,37 @@ static function($assert, $values) {
$assert->same(2, $loaded);
},
);

yield proof(
'Identity::defer() holds intermediary values',
given(
Set\Type::any(),
Set\Type::any(),
),
static function($assert, $value1, $value2) {
$m1 = Identity::defer(static function() use ($value1) {
static $loaded = false;

if ($loaded) {
throw new Exception;
}

$loaded = true;

return $value1;
});
$m2 = $m1->map(static fn() => $value2);

$assert->same(
$value2,
$m2->unwrap(),
);
$assert->not()->throws(
static fn() => $assert->same(
$value1,
$m1->unwrap(),
),
);
},
);
};
31 changes: 31 additions & 0 deletions proofs/map.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
declare(strict_types = 1);

use Innmind\Immutable\Map;
use Innmind\BlackBox\Set;

return static function() {
yield proof(
'Map::toSequence()',
given(
Set\Sequence::of(Set\Type::any()),
Set\Sequence::of(Set\Type::any()),
),
static function($assert, $keys, $values) {
$map = Map::of();

foreach ($keys as $index => $key) {
$map = ($map)($key, $values[$index] ?? null);
}

$assert->true(
$map->equals(Map::of(
...$map
->toSequence()
->map(static fn($pair) => [$pair->key(), $pair->value()])
->toList(),
)),
);
},
);
};
46 changes: 46 additions & 0 deletions proofs/maybe.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
declare(strict_types = 1);

use Innmind\Immutable\Maybe;
use Innmind\BlackBox\Set;

return static function() {
yield proof(
'Maybe::defer() holds intermediary values',
given(
Set\Type::any(),
Set\Type::any(),
),
static function($assert, $value1, $value2) {
$m1 = Maybe::defer(static function() use ($value1) {
static $loaded = false;

if ($loaded) {
throw new Exception;
}

$loaded = true;

return Maybe::just($value1);
});
$m2 = $m1->map(static fn() => $value2);

$assert->same(
$value2,
$m2->match(
static fn($value) => $value,
static fn() => null,
),
);
$assert->not()->throws(
static fn() => $assert->same(
$value1,
$m1->match(
static fn($value) => $value,
static fn() => null,
),
),
);
},
);
};
73 changes: 73 additions & 0 deletions proofs/sequence.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,77 @@ static function($assert, $string, $chunk) {
);
},
);

yield proof(
'Sequende::defer() holds intermediary values even when no longer used',
given(
Set\Sequence::of(Set\Type::any()),
Set\Sequence::of(Set\Type::any()),
),
static function($assert, $prefix, $suffix) {
$initial = Sequence::defer((static function() use ($prefix, $suffix) {
foreach ($prefix as $value) {
yield $value;
}

foreach ($suffix as $value) {
yield $value;
}
})());

// This does a partial read on the generator
$assert->same(
$prefix,
$initial
->take(\count($prefix))
->toList(),
);

// The maps are only here to wrap the generator, it doesn't change
// the values
$another = $initial
->map(static fn($value) => [$value])
->map(static fn($values) => $values[0]);
unset($initial);

// If it didn't store the intermediary values the array would miss
// the prefix values due to the partial read on the initial
// generator due to the ->take()->toList() call above
$assert->same(
[...$prefix, ...$suffix],
$another->toList(),
);
},
);

yield proof(
"Sequence::defer() stack trace doesn't show intermediary sequences when not used",
given(Set\Integers::between(1, 10)),
static function($assert, $calls) {
$expected = null;
$sequence = Sequence::defer((static function() use (&$expected) {
yield null;

throw $expected = new Exception;
})());

for ($i = 0; $i < $calls; $i++) {
$sequence = $sequence->map(static fn($value) => $value);
}

try {
$sequence->toList();
$assert->fail('it should throw');
} catch (Exception $e) {
$assert->same($expected, $e);

$accumulations = \array_filter(
$e->getTrace(),
static fn($frame) => \str_ends_with($frame['file'] ?? '', 'src/Accumulate.php'),
);

$assert->count(1, $accumulations);
}
},
);
};
Loading

0 comments on commit 6bef242

Please sign in to comment.