There are some quite important functions that are being very commonly used to transform data, even across languages, that modern approaches to solving problems greatly prefer. Many things in theory could fit such definition, but right now I am talking about map, reduce or even filter functions, all of which are being increasingly preferred to plain while, for and foreach loops, wherever applicable. Of course Laravel offers it's flavor of these functions that work on data in Collections. I will not detail on how to use them as the official documentation for filter, map, and reduce respectively is detailed enough. Instead I want to focus here on a small bit that is omitted in the docs. Using keys with reduce.

Reduce

From the official docs mentioned above, the example for reduce looks like this:

<?php
$collection = collect([1, 2, 3]);

$total = $collection->reduce(function ($carry, $item) {
    return $carry + $item;
});

// 6

And in fact, doing a sum of values is one of the most used example for reduce usage there is. Basically the book example. You will probably find similar examples for other languages too.

Reduce with an arrow function

For the sake of improvement, let's rewrite the above using arrow function, a feature that had been added into PHP as a part of anonymous functions. They are available in javascript as well and I love using them there, so let's try:

<?php
$collection = collect([1, 2, 3]);

$total = $collection->reduce(fn ($carry, $item) =>
  $carry + $item;
);

// 6

Saves a few keystrokes, too. By now, it should be clear even to the young Padawans that the reduce function for it's first argument accepts a callback function, that has two arguments, a $carry and the actual $item being iterated, many times referred to as a value. If we really just want to do a sum of values, this is all we need. What about situations where it is not enough?

Reduce with keys

Imagine we have a Collection of cities and we want to use reduce to calculate the total distance between them using reduce alone. This is little bit tricky because the distance is a relation between two cities so we have to have away to access it within a callback. Not being able to find reliably the documentation for this, I decided to quickly write it down, so here it is:

<?php
$cities = City::all();

$total = $cities->reduce(function ($carry, $city, $key) use ($cities) {
  $next = $key + 1;

  if (isset($cities[$next]) {
    $carry += $city->distanceTo($cities[$next]);
  }

  return $carry;
});

The most important bit here is that the callback can actually have more than two parameters, the third one is being the $key. We also have to make $cities available in the local scope via use keyword and need to check if the end of the array was not reached beforehand.

Show me them arrows

Arrow functions in javascript can have many statements. In PHP, only a single assignment per arrow function is permitted. Rewriting the above with an arrow function is trickier, but possible.

<?php
$cities = City::all();

$total = $cities->reduce(fn ($carry, $city, $key) =>
  isset($cities[$key + 1])
  ? $carry += $city->distanceTo($cities[$key + 1])
  : $carry
);

A ternary operator is used here. It is up to the reader to judge if this is an improvement or a hit to the readability. Also, there seems to exist too many ways people prefer to see the above code formatted, so it might even look scary or ugly to some. With arrow function however, outside variables are available in the local scope. Thus $cities are available without the need for the use keyword.

Better to split up

Using keys with reduce function, a part of Laravel Collections can be useful in some situations. Documentation does not explicitly mention the third parameter for the callback function and since, as demonstrated above, the code that makes use of it is not that elegant, maybe it is omitted for a good reason.

Is there another way? Well, as with anything programming related, the answer is yes. The $key is actually just the second attribute to the map function, and mentioned in the docs, go check it. In many situations using map with the keys in the similar fashion as above is better, as it would enable us running reduce on the mapped values, for example distances. It requires two functions instead of single concise one, but the resulting code might be more explicit. See below:

<?php
$cities = City::all();

$distances = $cities->map(fn ($city, $key) =>
  isset($cities[$key + 1])
  ? $city->distanceTo($cities[$key + 1])
  : 0
);

$total = $distances->reduce(fn ($carry, $distance) =>
  $carry += $distance
);

This is how I like to write it with my current style. What would be your preferred way?