jeremeamia/iter8

0.1.0 2019-07-19 22:59 UTC

This package is auto-updated.

Last update: 2024-09-20 10:20:02 UTC


README

Made with Love Coded in PHP Packagist Version CircleCI

简介

Iter8 是一个使用生成器实现的 PHP 库,用于迭代和函数式操作(例如 map、filter、reduce)。

Iter8 提供了创建和转换任何 iterable(例如生成器、迭代器、数组等)的方法,以便轻松处理符合 Iterator 模式用例的数据集(即,通常是大型、分页、无限或长度未知的数据)。使用迭代器/生成器通常可以提供降低内存消耗和惰性评估等好处。可以通过函数组合来定义复杂的转换。

用法

Iter8 的核心实现位于 3 个类中作为静态方法

  • Iter – 对可迭代值的操作。一些转换(例如 map、filter)和一些评估(例如 reduce)。
  • Gen – 从其他值创建可迭代对象的工厂。
  • Func – 用于创建或转换可调用对象以用于可迭代操作的实用工具。

Iter8 中有 3 种处理可迭代值的用法模式。您使用哪种模式主要取决于个人偏好

  • Iter 函数 – 使用 Iter 函数的标准面向函数的使用。
  • Pipe 组合 – 使用 Iter::pipe()Pipe::* 函数组合一系列可迭代转换。
  • Collection 对象 - Iter8 的面向对象的接口,暴露了 Iter::*Gen::* 函数作为集合类型对象上的可链接方法。

下一节中的示例将演示这些用法模式中的每一个。

示例

给定以下数据集...

const PEOPLE = [
    ['name' => 'Abby',  'age' => 19],
    ['name' => 'Benny', 'age' => 21],
    ['name' => 'Cally', 'age' => 22],
    ['name' => 'Danny', 'age' => 24],
    ['name' => 'Danny', 'age' => 24],
    ['name' => 'Eddy',  'age' => 18],
];

Iter 函数

在此用法模式中,操作按程序性和逐个应用。

$iter = Gen::from(PEOPLE);
$iter = Iter::filter($iter, Func::compose([
    Func::index('age'),
    Func::operator('>=', 20),
]));
$iter = Iter::map($iter, Func::index('name'));
$iter = Iter::debounce($iter);

Iter::print($iter);
#> ['Benny', 'Cally', 'Danny']

Pipe 组合

在此用法模式中,操作“管道”或组合在一起。`Pipe` 类将其操作委派给 `Iter` 类,但管理可迭代值。

$iter = Iter::pipe(Gen::from(PEOPLE), [
    Pipe::filter(Func::compose([
        Func::index('age'),
        Func::operator('>=', 20),
    ])),
    Pipe::map(Func::index('name')),
    Pipe::debounce(),
]);

Iter::print($iter);
#> ['Benny', 'Cally', 'Danny']

您可以在管道中间“切换”正在转换的可迭代值的上下文。此示例评估人员的最大年龄,然后切换到使用该最大年龄值的新可迭代值。

$iter = Iter::pipe(Gen::from(PEOPLE), [
    Pipe::pluck('age'),
    Pipe::reduce('max'),
    Pipe::switch(function (int $maxAge) {
        return Gen::range(1, $maxAge);
    }),
]);

Iter::print($iter);
#> [1, 2, 3, ..., 22, 23, 24]

Collection 对象

在此用法模式中,可迭代值封装为 Collection 对象。在集合对象上调用方法将委派回 `Iter` 类,但可迭代值在内部管理。集合是不可变的,因此每个转换都返回一个新的实例。此外,与常规生成器不同,集合可以被重置。`Collection` 上的静态方法调用委派给 `Gen`,因此 `Collection` 对象实际上从一个接口暴露了 Iter8 的全部功能。

$collection = Collection::from(PEOPLE)
    ->filter(Func::compose([
        Func::index('age'),
        Func::operator('>=', 20),
    ]))
    ->map(Func::index('name'))
    ->debounce();

$collection->print();
#> ['Benny', 'Cally', 'Danny']

可重置性

生成器是不可重置的(即,显式调用 rewind() 或再次迭代它们将导致错误)。Iter8 提供了两种使生成器/可迭代对象可重置的方法。

延迟生成器

如果您控制生成生成器的函数(即包含 yield 语句的函数),则可以使用 Gen::defer() 函数包装该生成函数。

$items = Gen::defer(function () use ($apiClient) {
    $apiResult = $apiClient->getItems();
    foreach ($apiResult['items'] as $data) {
        yield Models\Item::fromArray($data);
    }
});

// ...
// First iteration
foreach ($items as $item) { /* ... */ }
// ...
// Another iteration
foreach ($items as $item) { /* ... */ }

Gen::defer() 返回一个 DeferredGenerator 迭代器,它延迟在迭代时产生实际的生成器。如果您重置或再次迭代,则生成函数将重新执行。

可重置迭代器

如果您不控制生成函数或想避免重新执行生成器,那么您可以使用 Iter::rewindable() 函数使可迭代对象具有可回滚功能。

$apiResult = $apiClient->getItems();
$items = Iter::map($apiResult['items'], function (array $data) {
    return Models\Item::fromArray($data);
});
$items = Iter::rewindable($items);

// ...
// First iteration
foreach ($items as $item) { /* ... */ }
// ...
// Another iteration
foreach ($items as $item) { /* ... */ }

Iter::rewindable() 将提供的可迭代对象包装在一个 RewindableIterator 中,它在第一次迭代期间缓存项,以便在后续迭代中重新发射。

此外,由于它们扩展了 RewindableIteratorCollection 默认是可回滚的。

灵感来源

我最近在 PHP 中的许多工作都涉及到迭代器和生成器,以及处理大量的 API 结果集,所以我想要整理一些内容来分享。

然而,在诸如 nikic/iter 这样的库中已经完成了一些类似的工作。您会注意到我有一些类似的函数和实现。我的一些工作受到了该库的启发,还有一些是直接借鉴的。

此外,我还从 ReactiveX 项目中获得了灵感。尽管生成器和可观察者不是完全相同的概念,但它们是相似的,因此当这些操作和操作名称对生成器和可观察者都同样适用时,我会借鉴它们。此外,来自 RxJS(ReactiveX 的 JavaScript 实现)的“管道”概念也被用于此项目中以组合转换。从某种意义上说,这也类似于 mtdowling/transducers.php 库如何使用其“可组合算法转换”。而“转换器”的概念本身是从 Clojure 借鉴的,所以我从多个来源寻找灵感。

最后,我还从 Laravel Collections 库中获得了灵感。尽管我也有一些类似的函数,但我的实现有很大的差异,因为它们基于生成器,而不是数组。这意味着不支持在 Iter8 集合中对值的随机数组访问。