krak/fn

支持正确柯里化的PHP函数库

v1.4.0 2022-12-07 16:43 UTC

README

又一个PHP函数库。这个库的特殊之处在于它使用PHP解析器来生成非柯里化实现的柯里化版本,以获得最佳性能。

安装

使用composer在krak/fn下安装

使用方法

所有函数都在Krak\Fun中定义,不是柯里化的,并且数据放在最后。函数的柯里化版本在Kran\Fun\Curried中定义。常量也按函数生成,在Krak\Fun\Consts中。

<?php

use function Krak\Fun\Curried\{filter, map, op};
use function Krak\Fun\{compose};
use const Krak\Fun\Consts\{toArray};

$res = compose(
    toArray,
    map(op('*')(3)),
    filter(op('>')(2))
)([1,2,3,4]);
assert($res == [9, 12]);

查看src/fn.php以获取所有函数的示例。

Fun API

除了单独导入函数/常量外,您还可以利用fc命名空间作为快捷方式,这使得使用库变得更容易。

<?php

use Krak\Fun\{f, c};

$res = f\compose(
    c\toArray,
    c\map(c\op('*')(3)),
    c\filter(c\op('>')(2))
)([1,2,3,4]);
assert($res == [9, 12]);

f命名空间包含基本上直接从Krak\Fun命名空间复制过来的标准函数。

c命名空间包含所有柯里化函数和常量定义。

使用常量的一个很好的方法是使用组合或管道链

use Krak\Fun\{f, c};

$res = f\compose(
    c\toArray,
    c\map(function($tup) {
        return $tup[0] + $tup[1];
    }),
    c\toPairs
)([1,2,3]);
// $res == [1, 3, 5]

常量

此库生成与生成它们的函数同名的常量,其值是该函数的完全限定名称。

PHP(遗憾的是)会将字符串视为可调用的,如果它们解析为函数名称。因此,生成与函数同名的常量使我们能够支持整洁的一等函数类型语法。

<?php

use Krak\Fun\{f, c};

function getArrayRange(callable $toArray): array {
    $toArray(f\range(1,3));
}

getArrayRange(c\toArray);

上面的代码是有效的PHP,将正常工作,因为c\toArray解析为Krak\\Fun\\toArray,PHP将其视为有效的可调用对象。

这对于组合链和部分应用非常出色

use Krak\Fun\{f, c};

$res = f\compose(
    c\toArray,
    map(partial(c\op, '*', 3))
)([1,2,3]);
assert($res == [3,6,9]);

op函数定义为op($operator, $b, $a)。本质上,我们做了如下操作:partial('Krak\\Fun\\op', '*', 3)

柯里化

所有可柯里化的函数都生成了柯里化函数。一个函数是可柯里化的,如果它有多个必需参数或一个必需参数带有任意数量的可选参数。

以下函数定义不可柯里化

func()
func($arg1)
func($oarg = null, $oarg1 = null)

以下是可柯里化的

func($arg1, $arg2)
func($arg1, $oarg = null)

给定一个函数定义,例如

(a, b, c = null) -> Void

柯里化版本将看起来像

(a, c = null) -> (b) -> Void

调试

如果您有一个函数组合链,并想调试/测试任何函数的结果,您可以像以下示例那样操作

  1. 调试单个值

    f\compose(
        function() {}, // do something else
        c\dd(), // debug result here
        function() {}, // do another thing that returns a single value
        function() {} // do something
    );
  2. 调试可迭代对象

    f\compose(
        function() {}, // do something else
        c\dd(), c\toArray, // debug result here
        c\map(function() {}) // do something
    );

使用组合链编写可读代码

使用这个库的一个我最喜欢的功能是构建组合链,使您的应用程序服务更容易阅读和跟踪。

use Krak\Fun\{f, c};

/**
 * Fetches orders based off of the arguments, filters data, and imports to database
 */
final class ImportOrdersFromApi
{
    public function __construct(ApiClient $client, OrderRepository $orderRepository, SaveOrders $saveOrders) {
        // ...
    }

    public function __invoke(ImportOrderRequest $req): void {
        f\compose(
            $this->persistApiOrders(),
            $this->removeAlreadyImportedOrders(),
            $this->fetchOrdersFromApi()
        )($req);
    }

    private function persistApiOrders(): callable {
        // important that this is an c\each so that it will consume the iterable chain
        return f\compose(
            c\each($this->saveOrders), // saveOrders has a signature of `__invoke(iterable Order[]) => void`
            c\chunk(50), // chunk to persist many at once
            c\map(function(array $apiOrder) {
                return Order::createFromApiData($apiOrder);
            })
        );
    }

    private function removeAlreadyImportedOrders(): callable {
        return f\compose(
            c\flatMap(function(array $apiOrders) {
                $apiOrderIds = array_column($apiOrders, 'order_id');
                /** array of order id => order entity */
                $orders = $this->orderRepository->findByApiOrderIds($ids);
                return f\filter(function(array $apiOrder) use ($orders) {
                    return !array_key_exists($apiOrder['order_id'], $orders);
                }, $apiOrders);
            }),
            // chunk by 50 to save on database requests
            c\chunk(50)
        );
    }

    /** Returns an iterable of api orders */
    private function fetchOrdersFromApi(): callable {
        return function(ImportOrderRequest $req) {
            yield from $this->apiClient->fetch(/* pass in req args */);
        };
    }
}

文档

文档使用make docs生成。这使用Krak Peridocs从peridot测试中生成文档。

代码生成

常量和柯里化函数使用make code生成。

测试

测试通过make test运行,并存储在test目录中。我们使用peridot进行测试。

API

all(callable $predicate, iterable $iter): bool

名称: Krak\Fun\all

如果谓词在所有项上返回true,则返回true

$res = all(function ($v) {
    return $v % 2 == 0;
}, [2, 4, 6]);
expect($res)->equal(true);

如果谓词在任何项上返回false,则返回false

$res = all(function ($v) {
    return $v % 2 == 0;
}, [1, 2, 4, 6]);
expect($res)->equal(false);

any(callable $predicate, iterable $iter): bool

名称: Krak\Fun\any

如果谓词在任何项上返回true,则返回true

$res = any(function ($v) {
    return $v % 2 == 0;
}, [1, 3, 4, 5]);
expect($res)->equal(true);

如果谓词在所有项上返回false,则返回false

$res = any(function ($v) {
    return $v % 2 == 0;
}, [1, 3, 5]);
expect($res)->equal(false);

arrayCompact(iterable $iter): array

名称: Krak\Fun\arrayCompact

它将从可迭代对象中删除所有null,并返回一个数组

$res = arrayCompact([1, 2, null, null, 3]);
expect(\array_values($res))->equal([1, 2, 3]);

请注意,在调用arrayCompact时,键将被保留,因此如果您想忽略键,请确保使用array_values。

arrayFilter(callable $fn, iterable $data): array

名称: Krak\Fun\arrayFilter

是array_filter的别名

$res = arrayFilter(partial(op, '<', 2), [1, 2, 3]);
expect($res)->equal([1]);

同时过滤数组和可迭代对象

$res = arrayFilter(partial(op, '<', 2), range(1, 3));
expect($res)->equal([1]);

arrayMap(callable $fn, iterable $data): array

名称: Krak\Fun\arrayMap

是array_map的别名

$res = arrayMap(partial(op, '*', 2), [1, 2, 3]);
expect($res)->equal([2, 4, 6]);

同时映射数组和可迭代对象

$res = arrayMap(partial(op, '*', 2), range(1, 3));
expect($res)->equal([2, 4, 6]);

arrayReindex(callable $fn, iterable $iter): array

名称: Krak\Fun\arrayReindex

通过一个可调用的函数将集合重新索引为一个关联数组

$res = arrayReindex(function ($v) {
    return $v['id'];
}, [['id' => 2], ['id' => 3], ['id' => 1]]);
expect($res)->equal([2 => ['id' => 2], 3 => ['id' => 3], 1 => ['id' => 1]]);

arrayWrap($value)

名称: Krak\Fun\arrayWrap

将任何非列表数组包装为数组

$results = arrayMap(arrayWrap, [1, 'abc', ['a' => 1]]);
expect($results)->equal([[1], ['abc'], [['a' => 1]]]);

基于列表的数组保持不变

$results = arrayMap(arrayWrap, [[], [1, 2, 3]]);
expect($results)->equal([[], [1, 2, 3]]);

注意:需要php 8.1或symfony/polyfill-php81的array_is_list

assign($obj, iterable $iter)

名称: Krak\Fun\assign

将可迭代的键和值分配给一个对象

$obj = new \StdClass();
$obj = assign($obj, ['a' => 1, 'b' => 2]);
expect($obj->a)->equal(1);
expect($obj->b)->equal(2);

chain(iterable ...$iters)

名称: Krak\Fun\chain

将可迭代对象链接成一个可迭代对象

$res = chain([1], range(2, 3));
expect(toArray($res))->equal([1, 2, 3]);

chunk(int $size, iterable $iter): iterable

名称: Krak\Fun\chunk

将可迭代对象分成相等大小的块。

$res = chunk(2, [1, 2, 3, 4]);
expect(toArray($res))->equal([[1, 2], [3, 4]]);

如果有任何剩余部分,则按原样产生

$res = chunk(3, [1, 2, 3, 4]);
expect(toArray($res))->equal([[1, 2, 3], [4]]);

chunkBy(callable $fn, iterable $iter, ?int $maxSize = null): iterable

名称: Krak\Fun\chunkBy

根据可调用的结果将项目分组

$items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
$chunks = chunkBy(function (string $item) {
    return $item[0];
    // return first char
}, $items);
expect(toArray($chunks))->equal([['aa', 'ab', 'ac'], ['ba', 'bb', 'bc'], ['ca', 'cb', 'cc']]);

允许maxSize以防止块超过限制

$items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
$chunks = chunkBy(function (string $item) {
    return $item[0];
    // return first char
}, $items, 2);
expect(toArray($chunks))->equal([['aa', 'ab'], ['ac'], ['ba', 'bb'], ['bc'], ['ca', 'cb'], ['cc']]);

compact(iterable $iter): iterable

名称: Krak\Fun\compact

从可迭代对象中删除所有null值

$res = compact([1, null, 2, 3, null, null, 4]);
expect(toArray($res))->equal([1, 2, 3, 4]);

compose(callable ...$fns)

名称: Krak\Fun\compose

组合函数。compose(f, g)(x) == f(g(x))

$mul2 = Curried\op('*')(2);
$add3 = Curried\op('+')(3);
$add3ThenMul2 = compose($mul2, $add3);
$res = $add3ThenMul2(5);
expect($res)->equal(16);

允许一个空的初始参数

$res = compose(Curried\reduce(function ($acc, $v) {
    return $acc + $v;
}, 0), function () {
    yield from [1, 2, 3];
})();
expect($res)->equal(6);

construct($className, ...$args)

名称: Krak\Fun\construct

使用给定的参数构造(实例化)一个新类

$res = construct(\ArrayObject::class, [1, 2, 3]);
expect($res->count())->equal(3);

curry(callable $fn, int $num = 1)

名称: Krak\Fun\curry

将给定函数curry $n 次

$res = curry(_idArgs::class, 2)(1)(2)(3);
expect($res)->equal([1, 2, 3]);

给定一个函数定义:(a, b) -> c。curry版本将看起来像(a) -> (b) -> c

differenceWith(callable $cmp, iterable $a, iterable $b)

名称: Krak\Fun\differenceWith

使用给定的比较器获取两个可迭代对象的差

$res = differenceWith(partial(op, '==='), [1, 2, 3, 4, 5], [2, 3, 4]);
expect(toArray($res))->equal([1, 5]);

dd($value, callable $dump = null, callable $die = null)

名称: Krak\Fun\dd

打印和退出

$res = null;
$died = false;
$dump = function ($v) use(&$res) {
    $res = $v;
};
$die = function () use(&$died) {
    $died = true;
};
dd(1, $dump, $die);
expect($res)->equal(1);
expect($died)->equal(true);

drop(int $num, iterable $iter): iterable

名称: Krak\Fun\drop

从一个可迭代对象中删除前num个元素

$res = drop(2, range(0, 3));
expect(toArray($res))->equal([2, 3]);

dropWhile(callable $predicate, iterable $iter): iterable

名称: Krak\Fun\dropWhile

在谓词返回true时从可迭代对象中删除元素

$res = dropWhile(Curried\op('>')(0), [2, 1, 0, 1, 2]);
expect(toArray($res))->equal([0, 1, 2]);

each(callable $handle, iterable $iter)

名称: Krak\Fun\each

在可迭代对象的每个项目上调用一个可调用的函数

$state = [(object) ['id' => 1], (object) ['id' => 2]];
each(function ($item) {
    $item->id += 1;
}, $state);
expect([$state[0]->id, $state[1]->id])->equal([2, 3]);

通常使用php foreach足够遍历可迭代对象;然而,php变量在foreach循环中不是有范围的,而闭包是。

filter(callable $predicate, iterable $iter): iterable

名称: Krak\Fun\filter

惰性过滤可迭代对象,根据谓词返回true或false。如果为true,保留数据,否则从可迭代对象中删除数据

$values = filter(partial(op, '>', 2), [1, 2, 3, 4]);
// keep all items that are greater than 2
expect(toArray($values))->equal([3, 4]);

filterKeys(callable $predicate, iterable $iter): iterable

名称: Krak\Fun\filterKeys

根据键过滤可迭代对象

$res = filterKeys(Curried\inArray(['a', 'b']), ['a' => 1, 'b' => 2, 'c' => 3]);
expect(toArrayWithKeys($res))->equal(['a' => 1, 'b' => 2]);

flatMap(callable $map, iterable $iter): iterable

名称: Krak\Fun\flatMap

映射并展平一个可迭代对象

$res = flatMap(function ($v) {
    return [-$v, $v];
}, range(1, 3));
expect(toArray($res))->equal([-1, 1, -2, 2, -3, 3]);

flatMap非常适合当您想映射一个可迭代对象并添加元素到结果可迭代对象中时。

flatten(iterable $iter, $levels = INF): iterable

名称: Krak\Fun\flatten

将嵌套的可迭代对象展平成一个展平的元素集

$res = flatten([1, [2, [3, [4]]]]);
expect(toArray($res))->equal([1, 2, 3, 4]);

可以展平特定数量的级别

$res = flatten([1, [2, [3]]], 1);
expect(toArray($res))->equal([1, 2, [3]]);

展平零级别不执行任何操作

$res = flatten([1, [2]], 0);
expect(toArray($res))->equal([1, [2]]);

flip(iterable $iter): iterable

名称: Krak\Fun\flip

翻转可迭代对象的键 => 值为值 => 键

$res = flip(['a' => 0, 'b' => 1]);
expect(toArray($res))->equal(['a', 'b']);

fromPairs(iterable $iter): 可迭代对象

名称: Krak\Fun\fromPairs

将元组数组[$key, $value]的可迭代对象转换为关联可迭代对象

$res = fromPairs([['a', 1], ['b', 2]]);
expect(toArrayWithKeys($res))->equal(['a' => 1, 'b' => 2]);

groupBy(callable $fn, iterable $iter, ?int $maxSize = null): 可迭代对象

名称: Krak\Fun\groupBy

chunkBy的别名

根据函数的结果对项目进行分组

$items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
$groupedItems = groupBy(function (string $item) {
    return $item[0];
    // return first char
}, $items);
expect(toArray($groupedItems))->equal([['aa', 'ab', 'ac'], ['ba', 'bb', 'bc'], ['ca', 'cb', 'cc']]);

允许maxSize防止组超过限制

$items = ['aa', 'ab', 'ac', 'ba', 'bb', 'bc', 'ca', 'cb', 'cc'];
$groupedItems = groupBy(function (string $item) {
    return $item[0];
    // return first char
}, $items, 2);
expect(toArray($groupedItems))->equal([['aa', 'ab'], ['ac'], ['ba', 'bb'], ['bc'], ['ca', 'cb'], ['cc']]);

hasIndexIn(array $keys, array $data): bool

名称: Krak\Fun\hasIndexIn

检查嵌套索引是否存在于给定数据中

$res = hasIndexIn(['a', 'b', 'c'], ['a' => ['b' => ['c' => null]]]);
expect($res)->equal(true);

如果索引中的任何一个不存在于数据中,则返回false

$res = hasIndexIn(['a', 'b', 'c'], ['a' => ['b' => []]]);
expect($res)->equal(false);

head(iterable $iter)

名称: Krak\Fun\head

返回可迭代对象中的第一个元素

$res = head([1, 2, 3]);
expect($res)->equal(1);

如果可迭代对象为空,则返回null

$res = head([]);
expect($res)->equal(null);

inArray(array $set, $item): bool

名称: Krak\Fun\inArray

检查一个项目是否在项目数组中

$res = inArray([1, 2, 3], 2);
expect($res)->equal(true);

index($key, $data, $else = null)

名称: Krak\Fun\index

访问数组中的索引

$res = index('a', ['a' => 1]);
expect($res)->equal(1);

如果给定索引不存在值,则返回$else

$res = index('a', ['b' => 1], 2);
expect($res)->equal(2);

也适用于实现ArrayAccess的对象

class MyClass implements \ArrayAccess
{
    private $container = [];
    public function __construct()
    {
        $this->container = ['one' => 1, 'two' => 2];
    }
    public function offsetExists($offset)
    {
        return isset($this->container[$offset]);
    }
    public function offsetGet($offset)
    {
        return isset($this->container[$offset]) ? $this->container[$offset] : null;
    }
    public function offsetSet($offset, $value)
    {
        /* ... */
    }
    public function offsetUnset($offset)
    {
        /* ... */
    }
}
$object = new MyClass();
expect(index('two', $object))->equal(2);
expect(index('three', $object, 'else'))->equal('else');

indexIn(array $keys, array $data, $else = null)

名称: Krak\Fun\indexIn

访问深层数组结构中的嵌套索引

$res = indexIn(['a', 'b'], ['a' => ['b' => 1]]);
expect($res)->equal(1);

如果索引中存在任何不存在,则返回$else

$res = indexIn(['a', 'b'], ['a' => ['c' => 1]], 2);
expect($res)->equal(2);

indexOf(callable $predicate, iterable $iter)

名称: Krak\Fun\indexOf

搜索元素并返回找到的键

$res = indexOf(partial(op, '==', 'b'), ['a', 'b', 'c']);
expect($res)->equal(1);

isNull($val)

名称: Krak\Fun\isNull

is_null的别名

expect(isNull(null))->equal(true);
expect(isNull(0))->equal(false);

iter($iter): \Iterator

名称: Krak\Fun\iter

将任何可迭代对象转换为Iterator的适当实例。

可以转换数组

expect(iter([1, 2, 3]))->instanceof('Iterator');

可以转换Iterator

expect(iter(new \ArrayIterator([1, 2, 3])))->instanceof('Iterator');

可以转换对象

$obj = (object) ['a' => 1, 'b' => 2];
expect(iter($obj))->instanceof('Iterator');
expect(toArrayWithKeys(iter($obj)))->equal(['a' => 1, 'b' => 2]);

可以转换任何可迭代对象

$a = new class implements \IteratorAggregate
{
    public function getIterator()
    {
        return new \ArrayIterator([1, 2, 3]);
    }
};
expect(iter($a))->instanceof('Iterator');
expect(toArray(iter($a)))->equal([1, 2, 3]);

可以转换字符串

expect(iter('abc'))->instanceof('Iterator');
expect(toArray(iter('abc')))->equal(['a', 'b', 'c']);

否则将抛出异常

expect(function () {
    iter(1);
})->throw('LogicException', 'Iter could not be converted into an iterable.');

join(string $sep, iterable $iter)

名称: Krak\Fun\join

使用给定的分隔符连接可迭代对象

$res = join(",", range(1, 3));
expect($res)->equal("1,2,3");

keys(iterable $iter): 可迭代对象

名称: Krak\Fun\keys

仅产生可迭代对象的键

$keys = keys(['a' => 1, 'b' => 2]);
expect(toArray($keys))->equal(['a', 'b']);

map(callable $predicate, iterable $iter): 可迭代对象

名称: Krak\Fun\map

懒加载地将可迭代对象的值映射到另一个集合

$values = map(partial(op, '*', 2), [1, 2, 3, 4]);
expect(toArray($values))->equal([2, 4, 6, 8]);

mapAccum(callable $fn, iterable $iter, $acc = null)

名称: Krak\Fun\mapAccum

将函数映射到列表的每个元素,同时传递累加器以便在每次迭代中累积

$data = iter('abcd');
[$totalSort, $values] = mapAccum(function ($acc, $value) {
    return [$acc + 1, ['name' => $value, 'sort' => $acc]];
}, iter('abcd'), 0);
expect($totalSort)->equal(4);
expect($values)->equal([['name' => 'a', 'sort' => 0], ['name' => 'b', 'sort' => 1], ['name' => 'c', 'sort' => 2], ['name' => 'd', 'sort' => 3]]);

注意:mapAccum将可迭代对象转换为数组,与该库中的大多数其他函数不同,不是懒加载的

mapKeys(callable $predicate, iterable $iter): 可迭代对象

名称: Krak\Fun\mapKeys

懒加载地将可迭代对象的键映射到另一个集合

$keys = mapKeys(partial(op, '.', '_'), ['a' => 1, 'b' => 2]);
expect(toArrayWithKeys($keys))->equal(['a_' => 1, 'b_' => 2]);

mapKeyValue(callable $fn, iterable $iter): 可迭代对象

名称: Krak\Fun\mapKeyValue

懒加载地将可迭代对象的键/值元组映射到另一个集合

$keys = mapKeyValue(function ($kv) {
    [$key, $value] = $kv;
    return ["{$key}_", $value * $value];
}, ['a' => 1, 'b' => 2]);
expect(toArrayWithKeys($keys))->equal(['a_' => 1, 'b_' => 4]);

mapOn(array $maps, iterable $iter): 可迭代对象

名称: Krak\Fun\mapOn

针对特定键映射值

$values = mapOn(['a' => partial(op, '*', 3), 'b' => partial(op, '+', 1)], ['a' => 1, 'b' => 2, 'c' => 3]);
expect(toArray($values))->equal([3, 3, 3]);

nullable(callable $fn, $value)

名称: Krak\Fun\nullable

如果值不为null,则执行可调用函数

expect(nullable('intval', '0'))->equal(0);

如果值为null,则返回null

expect(nullable('intval', null))->equal(null);

onEach(callable $handle, iterable $iter)

名称: Krak\Fun\onEach

each的重复

在可迭代对象的每个项目上调用一个可调用的函数

$state = [(object) ['id' => 1], (object) ['id' => 2]];
onEach(function ($item) {
    $item->id += 1;
}, $state);
expect([$state[0]->id, $state[1]->id])->equal([2, 3]);

通常使用php foreach足够遍历可迭代对象;然而,php变量在foreach循环中不是有范围的,而闭包是。

op(string $op, $b, $a)

名称: Krak\Fun\op

op函数评估二元运算。它期望首先传入右操作符,这在进行柯里化或部分应用op函数时非常有用。在阅读op函数时,应该这样读:使用$b和$a评估$op,例如。

op('+', 2, 3) -> add 2 with 3
op('-', 2, 3) -> subtract 2 from 3
op('>', 2, 3) => compare greater than 2 with 3

使用给定的运算符评估两个值

$res = op('<', 2, 1);
expect($res)->equal(true);

支持相等运算符

$obj = new stdClass();
$ops = [['==', [1, 1]], ['eq', [2, 2]], ['!=', [1, 2]], ['neq', [2, 3]], ['===', [$obj, $obj]], ['!==', [new stdClass(), new stdClass()]], ['>', [1, 2]], ['gt', [1, 3]], ['>=', [1, 2]], ['gte', [1, 1]], ['<', [2, 1]], ['lt', [3, 1]], ['<=', [2, 1]], ['lte', [1, 1]]];
foreach ($ops as list($op, list($b, $a))) {
    $res = op($op, $b, $a);
    expect($res)->equal(true);
}

支持其他运算符

$ops = [['+', [2, 3], 5], ['-', [2, 3], 1], ['*', [2, 3], 6], ['**', [2, 3], 9], ['/', [2, 3], 1.5], ['%', [2, 3], 1], ['.', ['b', 'a'], 'ab']];
foreach ($ops as list($op, list($b, $a), $expected)) {
    $res = op($op, $b, $a);
    expect($res)->equal($expected);
}

部分应用或柯里化时更有用

$add2 = Curried\op('+')(2);
$mul3 = partial(op, '*', 3);
$sub4 = Curried\op('-')(4);
// ((2 + 2) * 3) - 4
$res = compose($sub4, $mul3, $add2)(2);
expect($res)->equal(8);

pad(int $size, iterable $iter, $padValue = null): iterable

名称: Krak\Fun\pad

将可迭代对象填充到特定大小

$res = pad(5, [1, 2, 3]);
expect(toArray($res))->equal([1, 2, 3, null, null]);

允许自定义填充值

$res = pad(5, [1, 2, 3], 0);
expect(toArray($res))->equal([1, 2, 3, 0, 0]);

如果可迭代对象的大小与填充大小相同,则不填充任何内容

$res = pad(5, [1, 2, 3, 4, 5]);
expect(toArray($res))->equal([1, 2, 3, 4, 5]);

如果可迭代对象的大小大于填充大小,则不填充任何内容

$res = pad(5, [1, 2, 3, 4, 5, 6]);
expect(toArray($res))->equal([1, 2, 3, 4, 5, 6]);

忽略原始可迭代对象的键

$res = pad(3, ['a' => 1, 'b' => 2]);
expect(toArrayWithKeys($res))->equal([1, 2, null]);

partial(callable $fn, ...$appliedArgs)

名称: Krak\Fun\partial

对函数部分应用参数。给定一个函数签名如f = (a, b, c) -> d,partial(f, a, b) -> (c) -> d

$fn = function ($a, $b, $c) {
    return ($a + $b) * $c;
};
$fn = partial($fn, 1, 2);
// apply the two arguments (a, b) and return a new function with signature (c) -> d
expect($fn(3))->equal(9);

在部分应用参数时也可以使用占位符

$fn = function ($a, $b, $c) {
    return ($a + $b) * $c;
};
// _() represents a placeholder for parameter b.
$fn = partial($fn, 1, _(), 3);
// create the new func with signature (b) -> d
expect($fn(2))->equal(9);

完整的部分应用也有效

$fn = function ($a, $b) {
    return [$a, $b];
};
$fn = partial($fn, 1, 2);
expect($fn())->equal([1, 2]);

partition(callable $partition, iterable $iter, int $numParts = 2): array

名称: Krak\Fun\partition

根据谓词将可迭代对象拆分为不同的数组

list($left, $right) = partition(function ($v) {
    return $v < 3 ? 0 : 1;
}, [1, 2, 3, 4]);
expect([$left, $right])->equal([[1, 2], [3, 4]]);

pick(iterable $fields, array $data): array

名称: Krak\Fun\pick

从结构化数组中仅选择指定的字段

$res = pick(['a', 'b'], ['a' => 1, 'b' => 2, 'c' => 3]);
expect($res)->equal(['a' => 1, 'b' => 2]);

可以使用柯里化形式

$res = arrayMap(Curried\pick(['id', 'name']), [['id' => 1, 'name' => 'Foo', 'slug' => 'foo'], ['id' => 2, 'name' => 'Bar', 'slug' => 'bar']]);
expect($res)->equal([['id' => 1, 'name' => 'Foo'], ['id' => 2, 'name' => 'Bar']]);

pickBy(callable $pick, array $data): array

名称: Krak\Fun\pickBy

从结构化数组中选择与pick函数匹配的字段

$res = pickBy(Curried\spread(function (string $key, int $value) : bool {
    return $value % 2 === 0;
}), ['a' => 1, 'b' => 2, 'c' => 3]);
expect($res)->equal(['b' => 2]);

pipe(callable ...$fns)

名称: Krak\Fun\pipe

创建一个函数,将值从一函数传递到另一函数。

$add3 = Curried\op('+')(3);
$mul2 = Curried\op('*')(2);
$add3ThenMul2 = pipe($add3, $mul2);
$res = $add3ThenMul2(5);
expect($res)->equal(16);

允许一个空的初始参数

$res = pipe(function () {
    yield from [1, 2, 3];
}, Curried\reduce(function ($acc, $v) {
    return $acc + $v;
}, 0))();
expect($res)->equal(6);

pipecompose是姐妹函数,它们做同样的事情,只是函数的组合顺序相反。pipe(f, g)(x) = g(f(x))

product(iterable ...$iters): iterable

名称: Krak\Fun\product

创建多个集合的笛卡尔积

$res = product([1, 2], [3, 4], [5, 6]);
expect(toArray($res))->equal([[1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6]]);

prop(string $key, $data, $else = null)

名称: Krak\Fun\prop

从对象中访问属性

$obj = new \StdClass();
$obj->id = 1;
$res = prop('id', $obj);
expect($res)->equal(1);

如果不存在属性,则返回$else值

$obj = new \StdClass();
$res = prop('id', $obj, 2);
expect($res)->equal(2);

propIn(array $props, $obj, $else = null)

名称: Krak\Fun\propIn

从对象树中访问深层属性

$obj = new \StdClass();
$obj->id = 1;
$obj->child = new \StdClass();
$obj->child->id = 2;
$res = propIn(['child', 'id'], $obj);
expect($res)->equal(2);

如果树中缺少任何属性,则返回$else值

$obj = new \StdClass();
$obj->id = 1;
$obj->child = new \StdClass();
$res = propIn(['child', 'id'], $obj, 3);
expect($res)->equal(3);

range($start, $end, $step = null)

名称: Krak\Fun\range

创建一个从$start开始到$end(包含)的值范围的可迭代对象,每次增加$step

$res = range(1, 3);
expect(toArray($res))->equal([1, 2, 3]);

它还允许递减范围

$res = range(3, 1);
expect(toArray($res))->equal([3, 2, 1]);

如果提供的$step方向错误,将抛出异常

expect(function () {
    toArray(range(1, 2, -1));
})->throw(\InvalidArgumentException::class);
expect(function () {
    toArray(range(2, 1, 1));
})->throw(\InvalidArgumentException::class);

reduce(callable $reduce, iterable $iter, $acc = null)

名称: Krak\Fun\reduce

将可迭代对象归约为一个单一值

$res = reduce(function ($acc, $v) {
    return $acc + $v;
}, range(1, 3), 0);
expect($res)->equal(6);

reduceKeyValue(callable $reduce, iterable $iter, $acc = null)

名称: Krak\Fun\reduceKeyValue

将可迭代对象的键值对归约为一个值

$res = reduceKeyValue(function ($acc, $kv) {
    [$key, $value] = $kv;
    return $acc . $key . $value;
}, fromPairs([['a', 1], ['b', 2]]), "");
expect($res)->equal("a1b2");

reindex(callable $fn, iterable $iter): iterable

名称: Krak\Fun\reindex

通过可调用函数重新索引集合

$res = reindex(function ($v) {
    return $v['id'];
}, [['id' => 2], ['id' => 3], ['id' => 1]]);
expect(toArrayWithKeys($res))->equal([2 => ['id' => 2], 3 => ['id' => 3], 1 => ['id' => 1]]);

retry(callable $fn, $shouldRetry = null)

名称: Krak\Fun\retry

执行一个函数,如果抛出异常则重试

$i = 0;
$res = retry(function () use(&$i) {
    $i += 1;
    if ($i <= 1) {
        throw new \Exception('bad');
    }
    return $i;
});
expect($res)->equal(2);

仅重试$maxTries次,否则放弃并冒泡异常

expect(function () {
    $i = 0;
    retry(function () use(&$i) {
        $i += 1;
        throw new \Exception((string) $i);
    }, 5);
})->throw('Exception', '6');

重试直到$shouldRetry返回false

$i = 0;
expect(function () {
    $res = retry(function () use(&$i) {
        $i += 1;
        throw new \Exception((string) $i);
    }, function ($numRetries, \Throwable $t = null) {
        return $numRetries < 2;
    });
})->throw('Exception', '2');

将numRetries发送到主fn

$res = retry(function ($numRetries) {
    if (!$numRetries) {
        throw new Exception('bad');
    }
    return $numRetries;
}, 2);
expect($res)->equal(1);

请注意,maxTries确定重试的次数。这意味着函数将执行maxTries + 1次,因为第一次调用不是重试。

search(callable $predicate, iterable $iter)

名称: Krak\Fun\search

在集合中搜索一个元素,其中可调用函数返回true

$res = search(function ($v) {
    return $v['id'] == 2;
}, [['id' => 1], ['id' => 2], ['id' => 3]]);
expect($res)->equal(['id' => 2]);

如果没有找到元素,则返回 null

$res = search(function ($v) {
    return false;
}, [['id' => 1], ['id' => 2], ['id' => 3]]);
expect($res)->equal(null);

setIndex($key, $value, array $data)

名称: Krak\Fun\setIndex

在数组中设置索引

$res = setIndex('a', 1, []);
expect($res['a'])->equal(1);

setIndexIn(array $keys, $value, array $data)

名称: Krak\Fun\setIndexIn

在数组中设置嵌套索引

$res = setIndexIn(['a', 'b'], 1, ['a' => []]);
expect($res['a']['b'])->equal(1);

setProp(string $key, $value, $data)

名称: Krak\Fun\setProp

在对象中设置属性

$res = setProp('a', 1, (object) []);
expect($res->a)->equal(1);

slice(int $start, iterable $iter, $length = INF): iterable

名称: Krak\Fun\slice

从可迭代对象中取出从起始位置到指定长度的包含切片

$sliced = slice(1, range(0, 4), 2);
expect(toArray($sliced))->equal([1, 2]);

如果未提供长度,则默认为可迭代对象的末尾

$sliced = slice(2, range(0, 4));
expect(toArray($sliced))->equal([2, 3, 4]);

一旦生成了切片,则不会消耗迭代器

$i = 0;
$gen = function () use(&$i) {
    foreach (range(0, 4) as $v) {
        $i = $v;
        (yield $i);
    }
};
$sliced = toArray(slice(1, $gen(), 2));
expect($sliced)->equal([1, 2]);
expect($i)->equal(2);

sortFromArray(callable $fn, array $orderedElements, iterable $iter): array

名称: Krak\Fun\sortFromArray

使用给定的有序元素数组对可迭代对象进行排序

$data = [['id' => 1, 'name' => 'A'], ['id' => 2, 'name' => 'B'], ['id' => 3, 'name' => 'C']];
$res = sortFromArray(Curried\index('id'), [2, 3, 1], $data);
expect(arrayMap(Curried\index('name'), $res))->equal(['B', 'C', 'A']);

如果可迭代对象中的任何项目不在有序元素中,则抛出异常

expect(function () {
    $data = [['id' => 1]];
    $res = sortFromArray(Curried\index('id'), [], $data);
})->throw(\LogicException::class, 'Cannot sort element key 1 because it does not exist in the ordered elements.');

这在从数据库中通过 WHERE IN 子句检索记录并确保结果按 WHERE IN 子句中的 id 的顺序排列时非常有用。

spread(callable $fn, array $data)

名称: Krak\Fun\spread

将数组参数展开到可调用函数中

$res = spread(function ($a, $b) {
    return $a . $b;
}, ['a', 'b']);
expect($res)->equal('ab');

可以用作柯里化的形式来解包元组参数

$res = arrayMap(Curried\spread(function (string $first, int $second) {
    return $first . $second;
}), [['a', 1], ['b', 2]]);
expect($res)->equal(['a1', 'b2']);

注意:这基本上就是 call_user_func_array 的别名,或者简单地是 ...(展开)操作符的功能包装。

take(int $num, iterable $iter): iterable

名称: Krak\Fun\take

从一个可迭代对象中取出前 num 个元素

$res = take(2, range(0, 10));
expect(toArray($res))->equal([0, 1]);

takeWhile(callable $predicate, iterable $iter): iterable

名称: Krak\Fun\takeWhile

在给定的断言函数返回 true 的情况下从可迭代对象中取出元素

$res = takeWhile(Curried\op('>')(0), [2, 1, 0, 1, 2]);
expect(toArray($res))->equal([2, 1]);

tap(callable $tap, $value)

名称: Krak\Fun\tap

在值上调用给定的 tap 函数并返回值

$loggedValues = [];
$res = tap(function (string $v) use(&$loggedValues) {
    $loggedValues[] = $v;
}, 'abc');
expect([$loggedValues[0], $res])->equal(['abc', 'abc']);

tap 在需要操作值但不希望修改返回值的情况下非常有用。

throwIf(callable $throw, callable $if, $value)

名称: Krak\Fun\throwIf

如果给定的值评估为 true,则抛出给定的异常

expect(function () {
    throwIf(function (int $value) {
        return new RuntimeException('Error: ' . $value);
    }, function (int $value) {
        return $value === 0;
    }, 0);
})->throw(RuntimeException::class, 'Error: 0');

如果给定的值评估为 false,则返回给定的值

$res = throwIf(function (int $value) {
    return new RuntimeException('Error: ' . $value);
}, function (int $value) {
    return $value === 0;
}, 1);
expect($res)->equal(1);

注意:与短闭包一起使用效果最佳!

toArray(iterable $iter): array

名称: Krak\Fun\toArray

将任何可迭代对象转换为数组

$res = toArray((function () {
    (yield 1);
    (yield 2);
    (yield 3);
})());
expect($res)->equal([1, 2, 3]);

也可以用作常量

$res = compose(toArray, id)((function () {
    (yield 1);
    (yield 2);
    (yield 3);
})());
expect($res)->equal([1, 2, 3]);

toArrayWithKeys(iterable $iter): array

名称: Krak\Fun\toArrayWithKeys

可以转换为数组并保留键

$gen = function () {
    (yield 'a' => 1);
    (yield 'b' => 2);
};
expect(toArrayWithKeys($gen()))->equal(['a' => 1, 'b' => 2]);

toPairs(iterable $iter): iterable

名称: Krak\Fun\toPairs

将关联数组转换为元组迭代器 [$key, $value]

$res = toPairs(['a' => 1, 'b' => 2]);
expect(toArray($res))->equal([['a', 1], ['b', 2]]);

updateIndexIn(array $keys, callable $update, array $data): array

名称: Krak\Fun\updateIndexIn

更新深层数组结构中的嵌套元素

$data = ['a' => ['b' => ['c' => 3]]];
$data = updateIndexIn(['a', 'b', 'c'], function ($v) {
    return $v * $v;
}, $data);
expect($data)->equal(['a' => ['b' => ['c' => 9]]]);

如果嵌套键不存在,则抛出异常

expect(function () {
    $data = ['a' => ['b' => ['c' => 9]]];
    updateIndexIn(['a', 'c', 'c'], function () {
    }, $data);
})->throw(\RuntimeException::class, 'Could not updateIn because the keys a -> c -> c could not be found.');

values(iterable $iter): iterable

名称: Krak\Fun\values

仅导出可迭代对象的值

$res = values(['a' => 1, 'b' => 2]);
expect(toArrayWithKeys($res))->equal([1, 2]);

when(callable $if, callable $then, $value)

名称: Krak\Fun\when

如果断言返回 true,则使用 $then 可调用函数评估给定的值

$if = function ($v) {
    return $v == 3;
};
$then = function ($v) {
    return $v * $v;
};
$res = when($if, $then, 3);
expect($res)->equal(9);

如果断言返回 false,则返回给定的值

$if = function ($v) {
    return $v == 3;
};
$then = function ($v) {
    return $v * $v;
};
$res = when($if, $then, 4);
expect($res)->equal(4);

withState(callable $fn, $initialState = null)

名称: Krak\Fun\withState

使用累积状态装饰函数

$fn = withState(function ($state, $v) {
    return [$state + 1, $state . ': ' . $v];
}, 1);
$res = arrayMap($fn, iter('abcd'));
expect($res)->equal(['1: a', '2: b', '3: c', '4: d']);

within(array $fields, iterable $iter): \Iterator

名称: Krak\Fun\within

仅允许给定的数组中的键保留

$data = flip(iter('abcd'));
$res = within(['a', 'c'], $data);
expect(toArrayWithKeys($res))->equal(['a' => 0, 'c' => 2]);

without(array $fields, iterable $iter): \Iterator

名称: Krak\Fun\without

筛选可迭代对象,移除指定的键

$data = flip(iter('abcd'));
$res = without(['a', 'c'], $data);
expect(toArrayWithKeys($res))->equal(['b' => 1, 'd' => 3]);

zip(iterable ...$iters): \Iterator

名称: Krak\Fun\zip

将多个可迭代对象合并成一个迭代器的 n 元组

$res = zip(iter('abc'), range(1, 3), [4, 5, 6]);
expect(toArray($res))->equal([['a', 1, 4], ['b', 2, 5], ['c', 3, 6]]);

如果没有迭代器存在,返回一个空的可迭代对象

expect(toArray(zip()))->equal([]);