pass only carry and value to two-parameter reducers

This commit is contained in:
Kacper Donat 2026-10-27 23:04:39 +01:00
parent d4f1130077
commit 4fe9da0167
2 changed files with 23 additions and 3 deletions

View File

@ -25,7 +25,6 @@ use function Kadet\Functional\Internal\callback;
use function Kadet\Functional\Collections\Internal\extremum;
use function Kadet\Functional\Collections\Internal\firstOf;
use function Kadet\Functional\Collections\Internal\reversedEntries;
use function Kadet\Functional\compile;
/**
* Returns the first value for which `$predicate($value, $key)` is truthy, or the first value without a predicate.
@ -288,6 +287,8 @@ function max(?callable $selector = null): \Closure
/**
* Folds the collection into a single value with `$fn($carry, $value, $key)`, starting from `$initial`.
*
* Internal functions (e.g. `max(...)`) and functions declaring two parameters get only `($carry, $value)`.
*
* @template TKey = mixed
* @template TValue = mixed
* @template TCarry = mixed
@ -299,8 +300,8 @@ function max(?callable $selector = null): \Closure
*/
function reduce(callable $fn, mixed $initial = null): \Closure
{
// The reducer takes the carry first, so it is compiled without the ($value, $key) adaptation.
$fn = compile($fn);
// The reducer takes the carry first: an internal one, or one declaring two parameters, gets ($carry, $value).
$fn = callback($fn, 2);
return static function (iterable $data) use ($fn, $initial) {
$carry = $initial;

View File

@ -9,6 +9,7 @@
use Ds\Map;
use Ds\Seq;
use Kadet\Functional as f;
use Kadet\Functional\Collections as c;
use Kadet\Functional\Exception\EmptyCollectionException;
@ -204,6 +205,24 @@ class QueriesTest extends \PHPUnit\Framework\TestCase
$this->assertNull(c\reduce(fn ($carry, $v) => $v)([]));
}
public function testReduceWithInternalFunctionGetsCarryAndValueOnly()
{
$this->assertSame(2, c\reduce(max(...), 0)([5 => 1, 9 => 2]));
}
public function testReduceWithDecoratedTwoParameterClosureGetsCarryAndValueOnly()
{
$calls = [];
$reducer = f\memoize(function ($carry, $v) use (&$calls) {
$calls[] = func_get_args();
return $carry + $v;
});
$this->assertSame(3, c\reduce($reducer, 0)(['a' => 1, 'b' => 2]));
$this->assertSame([[0, 1], [1, 2]], $calls);
}
public function testSum()
{
$this->assertSame(6, c\sum()([1, 2, 3]));