Skip to content

Commit 59442d8

Browse files
committed
Add traverseEitherMerge and traverseEitherKVMerge functions
1 parent b6bcd01 commit 59442d8

File tree

2 files changed

+137
-24
lines changed

2 files changed

+137
-24
lines changed

src/Fp/Functions/Collection/Traverse.php

Lines changed: 73 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Fp\Functional\Either\Either;
88
use Fp\Functional\Option\Option;
99
use Fp\Operations\TraverseEitherAccOperation;
10+
use Fp\Operations\TraverseEitherMergeOperation;
1011
use Fp\Operations\TraverseEitherOperation;
1112
use Fp\Operations\TraverseOptionOperation;
1213

@@ -88,6 +89,78 @@ function traverseEither(iterable $collection, callable $callback): Either
8889
return traverseEitherKV($collection, dropFirstArg($callback));
8990
}
9091

92+
/**
93+
* Same as {@see traverseEither()}, but passing also the key to the $callback function.
94+
*
95+
* @template E
96+
* @template TK of array-key
97+
* @template TV
98+
* @template TVO
99+
*
100+
* @param iterable<TK, TV> $collection
101+
* @param callable(TK, TV): Either<E, TVO> $callback
102+
* @return Either<E, array<TK, TVO>>
103+
*
104+
* @psalm-return (
105+
* $collection is non-empty-list ? Either<E, non-empty-list<TVO>> :
106+
* $collection is list ? Either<E, list<TVO>> :
107+
* $collection is non-empty-array ? Either<E, non-empty-array<TK, TVO>> :
108+
* Either<E, array<TK, TVO>>
109+
* )
110+
*/
111+
function traverseEitherKV(iterable $collection, callable $callback): Either
112+
{
113+
return TraverseEitherOperation::of($collection)($callback)->map(asArray(...));
114+
}
115+
116+
/**
117+
* Similar to {@see traverseEither} but collects all errors to non-empty-list.
118+
*
119+
* @template E
120+
* @template TK of array-key
121+
* @template TV
122+
* @template TVO
123+
*
124+
* @param iterable<TK, TV> $collection
125+
* @param callable(TV): Either<non-empty-list<E>, TVO> $callback
126+
* @return Either<non-empty-list<E>, array<TK, TVO>>
127+
*
128+
* @psalm-return (
129+
* $collection is non-empty-list ? Either<non-empty-list<E>, non-empty-list<TVO>> :
130+
* $collection is list ? Either<non-empty-list<E>, list<TVO>> :
131+
* $collection is non-empty-array ? Either<non-empty-list<E>, non-empty-array<TK, TVO>> :
132+
* Either<non-empty-list<E>, array<TK, TVO>>
133+
* )
134+
*/
135+
function traverseEitherMerge(iterable $collection, callable $callback): Either
136+
{
137+
return traverseEitherKVMerge($collection, dropFirstArg($callback));
138+
}
139+
140+
/**
141+
* Same as {@see traverseEitherMerge()}, but passing also the key to the $callback function.
142+
*
143+
* @template E
144+
* @template TK of array-key
145+
* @template TV
146+
* @template TVO
147+
*
148+
* @param iterable<TK, TV> $collection
149+
* @param callable(TK, TV): Either<non-empty-list<E>, TVO> $callback
150+
* @return Either<non-empty-list<E>, array<TK, TVO>>
151+
*
152+
* @psalm-return (
153+
* $collection is non-empty-list ? Either<non-empty-list<E>, non-empty-list<TVO>> :
154+
* $collection is list ? Either<non-empty-list<E>, list<TVO>> :
155+
* $collection is non-empty-array ? Either<non-empty-list<E>, non-empty-array<TK, TVO>> :
156+
* Either<non-empty-list<E>, array<TK, TVO>>
157+
* )
158+
*/
159+
function traverseEitherKVMerge(iterable $collection, callable $callback): Either
160+
{
161+
return TraverseEitherMergeOperation::of($collection)($callback)->map(asArray(...));
162+
}
163+
91164
/**
92165
* Same as {@see traverseEither()} but accumulates all left errors.
93166
*
@@ -141,27 +214,3 @@ function traverseEitherKVAcc(iterable $collection, callable $callback): Either
141214
})
142215
->map(asArray(...));
143216
}
144-
145-
/**
146-
* Same as {@see traverseEither()}, but passing also the key to the $callback function.
147-
*
148-
* @template E
149-
* @template TK of array-key
150-
* @template TV
151-
* @template TVO
152-
*
153-
* @param iterable<TK, TV> $collection
154-
* @param callable(TK, TV): Either<E, TVO> $callback
155-
* @return Either<E, array<TK, TVO>>
156-
*
157-
* @psalm-return (
158-
* $collection is non-empty-list ? Either<E, non-empty-list<TVO>> :
159-
* $collection is list ? Either<E, list<TVO>> :
160-
* $collection is non-empty-array ? Either<E, non-empty-array<TK, TVO>> :
161-
* Either<E, array<TK, TVO>>
162-
* )
163-
*/
164-
function traverseEitherKV(iterable $collection, callable $callback): Either
165-
{
166-
return TraverseEitherOperation::of($collection)($callback)->map(asArray(...));
167-
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Fp\Operations;
6+
7+
use Closure;
8+
use Fp\Collections\HashTable;
9+
use Fp\Functional\Either\Either;
10+
use Generator;
11+
12+
/**
13+
* @template TK
14+
* @template TV
15+
*
16+
* @extends AbstractOperation<TK, TV>
17+
*/
18+
final class TraverseEitherMergeOperation extends AbstractOperation
19+
{
20+
/**
21+
* @template E
22+
* @template TVO
23+
*
24+
* @param callable(TK, TV): Either<non-empty-list<E>, TVO> $f
25+
* @return Either<non-empty-list<E>, Generator<TK, TVO>>
26+
*/
27+
public function __invoke(callable $f): Either
28+
{
29+
/** @psalm-var HashTable<TK, TVO> */
30+
$rights = new HashTable();
31+
32+
$lefts = [];
33+
34+
foreach ($this->gen as $key => $value) {
35+
$mapped = $f($key, $value);
36+
37+
if ($mapped->isRight()) {
38+
$rights->update($key, $mapped->get());
39+
continue;
40+
}
41+
42+
foreach ($mapped->get() as $error) {
43+
$lefts[] = $error;
44+
}
45+
}
46+
47+
return !empty($lefts) ? Either::left($lefts) : Either::right($rights->getKeyValueIterator());
48+
}
49+
50+
/**
51+
* @template E
52+
* @template TKI
53+
* @template TVI
54+
*
55+
* @param iterable<TKI, Either<non-empty-list<E>, TVI> | Closure(): Either<non-empty-list<E>, TVI>> $collection
56+
* @return Either<non-empty-list<E>, Generator<TKI, TVI>>
57+
*/
58+
public static function id(iterable $collection): Either
59+
{
60+
return self::of($collection)(
61+
fn(mixed $_key, Either|Closure $i): Either => $i instanceof Closure ? $i() : $i
62+
);
63+
}
64+
}

0 commit comments

Comments
 (0)