Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[8.x] Add reduceMany to Collections #39078

Merged
merged 4 commits into from
Oct 5, 2021

Conversation

inxilpro
Copy link
Contributor

@inxilpro inxilpro commented Oct 4, 2021

Sometimes you need to compute multiple aggregate values from a collection. In many cases, it's fine to perform separate operations, but in cases where these values are interdependent, or there's a material performance cost to iterating the collection multiple times, this is not ideal.

This PR introduces a reduceMany operation that behaves exactly like reduce but allows for carrying as many values as needed. Rather than using the simplest example (see the added tests for this case), here's a slightly more complicated "real-world" example:

// Figure out how many images we can send to our image processor, given a fixed
// number of "credits" available (where each image may require more than 1 credit)
[$credits_remaining, $batch] = Image::unprocessed()->get()
    ->reduceMany(function($credits_remaining, $batch, $image) {
        if ($credits_remaining && $credits_remaining >= $image->creditsNecessary()) {
            $batch->push($image);
            $credits_remaining -= $image->creditsNecessary();
        }
        
        return [$credits_remaining, $batch];
    }, ImageProcessor::creditsAvailable(), collect());

ImageProcessor::processImages($batch);

if ($credits_remaining < 100) {
    Log::alert("Only $credits_remaining image processing credits remaining!");
}

(See this Twitter thread for another real-world use-case).

In the end, the API is exactly the same as reduce except that you can pass in more $initial values and receive a corresponding number of $carry values before the $value and $key. The only other difference is that you must return an array in your reducer that corresponds to the number of values you're reducing.

I've intentionally left this method off the Enumerable interface for backwards-compatibility reasons. If this PR gets merged, I would suggest adding it to the interface in 9.x and possibly updating the signature of reduce to behave the same way (which would be backwards compatible but change the interface and thus affect anyone who has a custom implementation).

@taylorotwell
Copy link
Member

Have you seen a similar method on any other client libraries in other languages?

@inxilpro
Copy link
Contributor Author

inxilpro commented Oct 5, 2021

Not exactly. JS benefits from destructuring assignment in the function parameters. And there are functional programming approaches like transducers that try to perform multiple operations in a single pass, but nothing with this API.

This came up because @DanielCoulbourne asked a question about it on Twitter and I had very recently had a conversation about exactly this with @cheyner — it just felt like too much of a coincidence :) There are "fine" approaches that you can use right now, but nothing that really feels right.

@taylorotwell taylorotwell merged commit 9c36d06 into laravel:8.x Oct 5, 2021
victorvilella pushed a commit to cdsistemas/framework that referenced this pull request Oct 12, 2021
* [8.x] Add `reduceMany` to Collections

* StyleCI

* StyleCI

* Update EnumeratesValues.php

Co-authored-by: Taylor Otwell <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants