hoist callback compilation out of transform closures

This commit is contained in:
Kacper Donat 2026-10-27 22:18:48 +01:00
parent b48f28e9dc
commit 04c40087c2
2 changed files with 49 additions and 18 deletions

View File

@ -44,9 +44,9 @@ use function Kadet\Functional\compile;
*/
function map(callable $fn): \Closure
{
return static function (iterable $input) use ($fn): iterable {
$fn = callback($fn);
$fn = callback($fn);
return static function (iterable $input) use ($fn): iterable {
return keyedLike($input, (static function () use ($input, $fn) {
foreach ($input as $key => $value) {
yield $key => $fn($value, $key);
@ -69,9 +69,9 @@ function map(callable $fn): \Closure
*/
function filter(?callable $fn = null): \Closure
{
return static function (iterable $input) use ($fn): iterable {
$fn = $fn === null ? static fn ($value) => $value : callback($fn);
$fn = $fn === null ? static fn ($value) => $value : callback($fn);
return static function (iterable $input) use ($fn): iterable {
return keyedLike($input, (static function () use ($input, $fn) {
foreach ($input as $key => $value) {
if ($fn($value, $key)) {
@ -94,10 +94,16 @@ function filter(?callable $fn = null): \Closure
*/
function reject(callable $fn): \Closure
{
return static function (iterable $input) use ($fn): iterable {
$fn = callback($fn);
$fn = callback($fn);
return filter(static fn ($value, $key) => !$fn($value, $key))($input);
return static function (iterable $input) use ($fn): iterable {
return keyedLike($input, (static function () use ($input, $fn) {
foreach ($input as $key => $value) {
if (!$fn($value, $key)) {
yield $key => $value;
}
}
})());
};
}
@ -116,9 +122,9 @@ function reject(callable $fn): \Closure
*/
function flatMap(callable $fn): \Closure
{
return static function (iterable $input) use ($fn): iterable {
$fn = callback($fn);
$fn = callback($fn);
return static function (iterable $input) use ($fn): iterable {
return listLike($input, (static function () use ($input, $fn) {
foreach ($input as $key => $value) {
$result = $fn($value, $key);
@ -192,9 +198,10 @@ function unique(): \Closure
*/
function uniqueBy(callable $selector): \Closure
{
$selector = callback($selector);
return static function (iterable $input) use ($selector): iterable {
$selector = callback($selector);
$seen = seen();
$seen = seen();
return keyedLike($input, (static function () use ($input, $selector, $seen) {
foreach ($input as $key => $value) {
@ -219,10 +226,10 @@ function uniqueBy(callable $selector): \Closure
*/
function sort(?callable $comparator = null): \Closure
{
return static function (iterable $input) use ($comparator): iterable {
// Comparators always take two values, so they are compiled without the ($value, $key) adaptation.
$comparator = $comparator === null ? static fn ($a, $b) => $a <=> $b : compile($comparator);
// Comparators always take two values, so they are compiled without the ($value, $key) adaptation.
$comparator = $comparator === null ? static fn ($a, $b) => $a <=> $b : compile($comparator);
return static function (iterable $input) use ($comparator): iterable {
$entries = entries($input);
usort($entries, static fn ($a, $b) => $comparator($a[1], $b[1]));
@ -245,9 +252,9 @@ function sort(?callable $comparator = null): \Closure
*/
function sortBy(callable $selector, bool $descending = false): \Closure
{
return static function (iterable $input) use ($selector, $descending): iterable {
$selector = callback($selector);
$selector = callback($selector);
return static function (iterable $input) use ($selector, $descending): iterable {
$entries = [];
foreach ($input as $key => $value) {
$entries[] = [$key, $value, $selector($value, $key)];
@ -304,6 +311,10 @@ function slice(int $offset, ?int $length = null): \Closure
*/
function take(int $n): \Closure
{
if ($n < 0) {
throw new \InvalidArgumentException(sprintf('take(): count must be >= 0, got %d', $n));
}
return slice(0, $n);
}
@ -314,6 +325,10 @@ function take(int $n): \Closure
*/
function skip(int $n): \Closure
{
if ($n < 0) {
throw new \InvalidArgumentException(sprintf('skip(): count must be >= 0, got %d', $n));
}
return slice($n);
}
@ -400,9 +415,9 @@ function merge(iterable $other): \Closure
*/
function each(callable $fn): \Closure
{
return static function (iterable $input) use ($fn): iterable {
$fn = callback($fn);
$fn = callback($fn);
return static function (iterable $input) use ($fn): iterable {
$reusable = is_array($input) || $input instanceof \Ds\Seq || $input instanceof \Ds\Map || $input instanceof \Ds\Set;
if ($reusable) {
foreach ($input as $key => $value) {

View File

@ -300,6 +300,22 @@ class TransformsTest extends \PHPUnit\Framework\TestCase
$this->assertSeq([2, 3], c\skip(1)(self::gen([1, 2, 3])));
}
public function testTakeRejectsNegativeCount()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('take(): count must be >= 0, got -1');
c\take(-1);
}
public function testSkipRejectsNegativeCount()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('skip(): count must be >= 0, got -1');
c\skip(-1);
}
// prepend / append / concat / merge
public function testPrepend()