Skip to content

Commit

Permalink
Merge pull request #26 from Innmind/reduce-circular-dependencies
Browse files Browse the repository at this point in the history
Reduce circular dependencies
  • Loading branch information
Baptouuuu authored Nov 9, 2024
2 parents ab7fe4e + a26cc19 commit 5182a4b
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 44 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

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

### Fixed

- Using `string`s or `int`s as a `Map` key type and then adding keys of different types was throwing an error.
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(),
),
);
},
);
};
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,
),
),
);
},
);
};
64 changes: 56 additions & 8 deletions src/Either/Defer.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,23 @@ public function __construct($deferred)

public function map(callable $map): self
{
return new self(fn() => $this->unwrap()->map($map));
$captured = $this->capture();

return new self(static fn() => self::detonate($captured)->map($map));
}

public function flatMap(callable $map): Either
{
return Either::defer(fn() => $this->unwrap()->flatMap($map));
$captured = $this->capture();

return Either::defer(static fn() => self::detonate($captured)->flatMap($map));
}

public function leftMap(callable $map): self
{
return new self(fn() => $this->unwrap()->leftMap($map));
$captured = $this->capture();

return new self(static fn() => self::detonate($captured)->leftMap($map));
}

public function match(callable $right, callable $left)
Expand All @@ -52,17 +58,23 @@ public function match(callable $right, callable $left)

public function otherwise(callable $otherwise): Either
{
return Either::defer(fn() => $this->unwrap()->otherwise($otherwise));
$captured = $this->capture();

return Either::defer(static fn() => self::detonate($captured)->otherwise($otherwise));
}

public function filter(callable $predicate, callable $otherwise): Implementation
{
return new self(fn() => $this->unwrap()->filter($predicate, $otherwise));
$captured = $this->capture();

return new self(static fn() => self::detonate($captured)->filter($predicate, $otherwise));
}

public function maybe(): Maybe
{
return Maybe::defer(fn() => $this->unwrap()->maybe());
$captured = $this->capture();

return Maybe::defer(static fn() => self::detonate($captured)->maybe());
}

/**
Expand All @@ -75,12 +87,16 @@ public function memoize(): Either

public function flip(): self
{
return new self(fn() => $this->unwrap()->flip());
$captured = $this->capture();

return new self(static fn() => self::detonate($captured)->flip());
}

public function eitherWay(callable $right, callable $left): Either
{
return Either::defer(fn() => $this->unwrap()->eitherWay($right, $left));
$captured = $this->capture();

return Either::defer(static fn() => self::detonate($captured)->eitherWay($right, $left));
}

/**
Expand All @@ -94,4 +110,36 @@ private function unwrap(): Either
*/
return $this->value ??= ($this->deferred)();
}

/**
* @return array{\WeakReference<self<L1, R1>>, callable(): Either<L1, R1>}
*/
private function capture(): array
{
/** @psalm-suppress ImpureMethodCall */
return [
\WeakReference::create($this),
$this->deferred,
];
}

/**
* @template A
* @template B
*
* @param array{\WeakReference<self<A, B>>, callable(): Either<A, B>} $captured
*
* @return Either<A, B>
*/
private static function detonate(array $captured): Either
{
[$ref, $deferred] = $captured;
$self = $ref->get();

if (\is_null($self)) {
return $deferred();
}

return $self->unwrap();
}
}
53 changes: 48 additions & 5 deletions src/Identity/Defer.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,32 @@ public function __construct(callable $value)

public function map(callable $map): self
{
/** @psalm-suppress ImpureFunctionCall */
return new self(fn() => $map($this->unwrap()));
$captured = $this->capture();

/**
* @psalm-suppress ImpureFunctionCall
* @psalm-suppress MixedArgument
*/
return new self(static fn() => $map(self::detonate($captured)));
}

public function flatMap(callable $map): Identity
{
/** @psalm-suppress ImpureFunctionCall */
return Identity::lazy(fn() => $map($this->unwrap())->unwrap());
$captured = $this->capture();

/**
* @psalm-suppress ImpureFunctionCall
* @psalm-suppress MixedArgument
*/
return Identity::defer(static fn() => $map(self::detonate($captured))->unwrap());
}

public function toSequence(): Sequence
{
$captured = $this->capture();

/** @psalm-suppress ImpureFunctionCall */
return Sequence::defer((fn() => yield $this->unwrap())());
return Sequence::defer((static fn() => yield self::detonate($captured))());
}

public function unwrap(): mixed
Expand All @@ -64,4 +76,35 @@ public function unwrap(): mixed

return $this->computed;
}

/**
* @return array{\WeakReference<Implementation<T>>, callable(): T}
*/
private function capture(): array
{
/** @psalm-suppress ImpureMethodCall */
return [
\WeakReference::create($this),
$this->value,
];
}

/**
* @template V
*
* @param array{\WeakReference<Implementation<V>>, callable(): V} $captured
*
* @return V
*/
private static function detonate(array $captured): mixed
{
[$ref, $value] = $captured;
$self = $ref->get();

if (\is_null($self)) {
return $value();
}

return $self->unwrap();
}
}
4 changes: 3 additions & 1 deletion src/Identity/Lazy.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ public function flatMap(callable $map): Identity

public function toSequence(): Sequence
{
return Sequence::lazy(fn() => yield $this->unwrap());
$value = $this->value;

return Sequence::lazy(static fn() => yield $value());
}

public function unwrap(): mixed
Expand Down
62 changes: 54 additions & 8 deletions src/Maybe/Defer.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,16 @@ public function __construct(callable $deferred)

public function map(callable $map): self
{
return new self(fn() => $this->unwrap()->map($map));
$captured = $this->capture();

return new self(static fn() => self::detonate($captured)->map($map));
}

public function flatMap(callable $map): Maybe
{
return Maybe::defer(fn() => $this->unwrap()->flatMap($map));
$captured = $this->capture();

return Maybe::defer(static fn() => self::detonate($captured)->flatMap($map));
}

public function match(callable $just, callable $nothing)
Expand All @@ -47,17 +51,23 @@ public function match(callable $just, callable $nothing)

public function otherwise(callable $otherwise): Maybe
{
return Maybe::defer(fn() => $this->unwrap()->otherwise($otherwise));
$captured = $this->capture();

return Maybe::defer(static fn() => self::detonate($captured)->otherwise($otherwise));
}

public function filter(callable $predicate): Implementation
{
return new self(fn() => $this->unwrap()->filter($predicate));
$captured = $this->capture();

return new self(static fn() => self::detonate($captured)->filter($predicate));
}

public function either(): Either
{
return Either::defer(fn() => $this->unwrap()->either());
$captured = $this->capture();

return Either::defer(static fn() => self::detonate($captured)->either());
}

/**
Expand All @@ -70,17 +80,22 @@ public function memoize(): Maybe

public function toSequence(): Sequence
{
$captured = $this->capture();

/** @psalm-suppress ImpureFunctionCall */
return Sequence::defer((function() {
foreach ($this->unwrap()->toSequence()->toList() as $value) {
return Sequence::defer((static function() use ($captured) {
/** @var V $value */
foreach (self::detonate($captured)->toSequence()->toList() as $value) {
yield $value;
}
})());
}

public function eitherWay(callable $just, callable $nothing): Maybe
{
return Maybe::defer(fn() => $this->unwrap()->eitherWay($just, $nothing));
$captured = $this->capture();

return Maybe::defer(static fn() => self::detonate($captured)->eitherWay($just, $nothing));
}

/**
Expand All @@ -94,4 +109,35 @@ private function unwrap(): Maybe
*/
return $this->value ??= ($this->deferred)();
}

/**
* @return array{\WeakReference<self<V>>, callable(): Maybe<V>}
*/
private function capture(): array
{
/** @psalm-suppress ImpureMethodCall */
return [
\WeakReference::create($this),
$this->deferred,
];
}

/**
* @template T
*
* @param array{\WeakReference<self<T>>, callable(): Maybe<T>} $captured
*
* @return Maybe<T>
*/
private static function detonate(array $captured): Maybe
{
[$ref, $deferred] = $captured;
$self = $ref->get();

if (\is_null($self)) {
return $deferred();
}

return $self->unwrap();
}
}
Loading

0 comments on commit 5182a4b

Please sign in to comment.