Skip to content

Commit

Permalink
Add sequenceEitherMerged
Browse files Browse the repository at this point in the history
  • Loading branch information
klimick committed Dec 5, 2022
1 parent 448e3a9 commit 68cf88f
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 2 deletions.
37 changes: 37 additions & 0 deletions src/Fp/Functions/Collection/Sequence.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Fp\Functional\Either\Either;
use Fp\Functional\Option\Option;
use Fp\Operations\TraverseEitherAccOperation;
use Fp\Operations\TraverseEitherMergedOperation;
use Fp\Operations\TraverseEitherOperation;
use Fp\Operations\TraverseOptionOperation;

Expand Down Expand Up @@ -70,6 +71,42 @@ function sequenceEither(iterable $collection): Either
return TraverseEitherOperation::id($collection)->map(asArray(...));
}

/**
* Same as {@see traverseEither()} but merge all left errors into non-empty-list.
*
* @template E
* @template TK of array-key
* @template TVI
*
* @param iterable<TK, Either<non-empty-list<E>, TVI> | Closure(): Either<non-empty-list<E>, TVI>> $collection
* @return Either<non-empty-list<E>, array<TK, TVI>>
* @psalm-return (
* $collection is non-empty-list ? Either<non-empty-list<E>, non-empty-list<TVI>> :
* $collection is list ? Either<non-empty-list<E>, list<TVI>> :
* $collection is non-empty-array ? Either<non-empty-list<E>, non-empty-array<TK, TVI>> :
* Either<non-empty-list<E>, array<TK, TVI>>
* )
*/
function sequenceEitherMerged(iterable $collection): Either
{
return TraverseEitherMergedOperation::id($collection)->map(asArray(...));
}

/**
* Varargs version of {@see sequenceEitherMerged()}.
*
* @template E
* @template TK of array-key
* @template TVI
*
* @param Either<non-empty-list<E>, TVI> | Closure(): Either<non-empty-list<E>, TVI> ...$items
* @return Either<non-empty-list<E>, list<TVI>>
*/
function sequenceEitherMergedT(Either|Closure ...$items): Either
{
return TraverseEitherMergedOperation::id($items)->map(asList(...));
}

/**
* Varargs version of {@see sequenceEither()}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
use Psalm\Type\Union;

use function Fp\Callable\ctor;
use function Fp\Collection\contains;
use function Fp\Collection\sequenceOptionT;
use function Fp\Evidence\of;
use function Fp\Evidence\proveTrue;
Expand All @@ -31,6 +32,8 @@ public static function getFunctionIds(): array
return [
strtolower('Fp\Collection\sequenceEither'),
strtolower('Fp\Collection\sequenceEitherT'),
strtolower('Fp\Collection\sequenceEitherMerged'),
strtolower('Fp\Collection\sequenceEitherMergedT'),
];
}

Expand Down Expand Up @@ -67,7 +70,12 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev
*/
private static function getInputTypeFromSequenceEither(FunctionReturnTypeProviderEvent $event): Option
{
return proveTrue(strtolower('Fp\Collection\sequenceEither') === $event->getFunctionId())
$isSequenceEither = contains($event->getFunctionId(), [
strtolower('Fp\Collection\sequenceEither'),
strtolower('Fp\Collection\sequenceEitherMerged'),
]);

return proveTrue($isSequenceEither)
->flatMap(fn() => PsalmApi::$args->getCallArgs($event))
->flatMap(fn($args) => $args->head())
->flatMap(fn(CallArg $arg) => PsalmApi::$types->asSingleAtomic($arg->type))
Expand All @@ -79,7 +87,12 @@ private static function getInputTypeFromSequenceEither(FunctionReturnTypeProvide
*/
private static function getInputTypeFromSequenceEitherT(FunctionReturnTypeProviderEvent $event): Option
{
return proveTrue(strtolower('Fp\Collection\sequenceEitherT') === $event->getFunctionId())
$isSequenceEitherT = contains($event->getFunctionId(), [
strtolower('Fp\Collection\sequenceEitherT'),
strtolower('Fp\Collection\sequenceEitherMergedT'),
]);

return proveTrue($isSequenceEitherT)
->flatMap(fn() => PsalmApi::$args->getCallArgs($event))
->flatMap(fn(ArrayList $args) => $args->toNonEmptyArrayList())
->map(fn(NonEmptyArrayList $args) => new TKeyedArray(
Expand Down
55 changes: 55 additions & 0 deletions tests/Runtime/Functions/Collection/EitherTraverseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

use function Fp\Collection\sequenceEither;
use function Fp\Collection\sequenceEitherAcc;
use function Fp\Collection\sequenceEitherMerged;
use function Fp\Collection\sequenceEitherMergedT;
use function Fp\Collection\sequenceEitherT;
use function Fp\Collection\traverseEither;
use function Fp\Collection\traverseEitherAcc;
Expand Down Expand Up @@ -140,6 +142,59 @@ public function testSequence(): void
);
}

public function testSequenceMerged(): void
{
$this->assertEquals(
Either::right([]),
sequenceEitherMerged([]),
);

$this->assertEquals(
Either::right([1, 2]),
sequenceEitherMerged([
Either::right(1),
Either::right(2),
])
);

$this->assertEquals(
Either::left(['error1']),
sequenceEitherMerged([
Either::right(1),
Either::left(['error1']),
])
);

$this->assertEquals(
Either::left(['error1', 'error2']),
sequenceEitherMerged([
Either::right(1),
Either::left(['error1']),
Either::left(['error2']),
])
);

$this->assertEquals(
Either::right([]),
sequenceEitherMergedT(),
);

$this->assertEquals(
Either::right([1, 2]),
sequenceEitherMergedT(Either::right(1), Either::right(2)),
);

$this->assertEquals(
Either::left(['error1']),
sequenceEitherMergedT(Either::right(1), Either::left(['error1']))
);

$this->assertEquals(
Either::left(['error1', 'error2']),
sequenceEitherMergedT(Either::right(1), Either::left(['error1']), Either::left(['error2']))
);
}

public function testLazySequence(): void
{
$this->assertEquals(
Expand Down
29 changes: 29 additions & 0 deletions tests/Static/Plugin/SequenceEitherPluginStaticTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
use function Fp\Collection\at;
use function Fp\Collection\sequenceEither;
use function Fp\Collection\sequenceEitherAcc;
use function Fp\Collection\sequenceEitherMerged;
use function Fp\Collection\sequenceEitherMergedT;
use function Fp\Collection\sequenceEitherT;
use function Fp\Evidence\proveInt;
use function Fp\Evidence\proveNonEmptyString;
Expand Down Expand Up @@ -150,6 +152,33 @@ public function sequenceEitherT(array $data): Either
);
}

/**
* @param array<string, mixed> $data
* @return Either<non-empty-list<-1|-2>, array{non-empty-string, int}>
*/
public function sequenceEitherMergedT(array $data): Either
{
return sequenceEitherMergedT(
at($data, 'name')->flatMap(proveNonEmptyString(...))->toRight(fn() => [-1]),
at($data, 'age')->flatMap(proveInt(...))->toRight(fn() => [-2]),
);
}

/**
* @param array<string, mixed> $data
* @return Either<non-empty-list<-1|-2>, array{
* n: non-empty-string,
* a: int,
* }>
*/
public function sequenceEitherMerged(array $data): Either
{
return sequenceEitherMerged([
'n' => at($data, 'name')->flatMap(proveNonEmptyString(...))->toRight(fn() => [-1]),
'a' => at($data, 'age')->flatMap(proveInt(...))->toRight(fn() => [-2]),
]);
}

/**
* @return Either<
* array{
Expand Down

0 comments on commit 68cf88f

Please sign in to comment.