krak / fn
支持正确柯里化的PHP函数库
Requires (Dev)
- krak/peridocs: dev-master
- lavoiesl/php-benchmark: ^1.4
- nikic/php-parser: ^4.0
- peridot-php/leo: ^1.6
- peridot-php/peridot: ^1.19
- symfony/polyfill-php81: ^1.27
- symfony/var-dumper: ^4.0
Suggests
- symfony/polyfill-php81: To use arrayWrap in PHP 7
- dev-main
- v1.4.0
- v1.3.0
- v1.2.0
- v1.1.0
- v1.0.0
- v0.1.30
- v0.1.29
- v0.1.28
- v0.1.27
- v0.1.26
- v0.1.25
- v0.1.24
- v0.1.23
- v0.1.22
- v0.1.21
- v0.1.20
- v0.1.19
- v0.1.18
- v0.1.17
- v0.1.16
- v0.1.15
- v0.1.14
- v0.1.13
- v0.1.12
- v0.1.11
- v0.1.10
- v0.1.9
- v0.1.8
- v0.1.7
- v0.1.6
- v0.1.5
- v0.1.4
- v0.1.3
- v0.1.2
- v0.1.1
- v0.1.0
- dev-fix-workflow
- dev-add-psalm-support
This package is auto-updated.
Last update: 2024-09-07 20:37:16 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
除了单独导入函数/常量外,您还可以利用f
和c
命名空间作为快捷方式,这使得使用库变得更容易。
<?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
调试
如果您有一个函数组合链,并想调试/测试任何函数的结果,您可以像以下示例那样操作
-
调试单个值
f\compose( function() {}, // do something else c\dd(), // debug result here function() {}, // do another thing that returns a single value function() {} // do something );
-
调试可迭代对象
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);
pipe
和compose
是姐妹函数,它们做同样的事情,只是函数的组合顺序相反。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([]);