Existujú niektoré pomerne dôležité funkcie, ktoré sa veľmi bežne používajú na transformáciu dát dokonca naprieč jazykmi, a ktoré moderné prístupy k riešeniu problémov výrazne preferujú. Mnohé veci by teoreticky mohli zapadať do takejto definície, ale teraz hovorím o funkciách map, reduce alebo filter, ktoré sú čoraz viac preferované pred jednoduchými cyklami while, for a foreach, kdekoľvek je to možné. Samozrejme Laravel ponúka svoju variantu týchto funkcií, ktoré pracujú s dátami v Collections. Nebudem podrobne opisovať, ako ich používať, keďže oficiálna dokumentácia pre filter, map a reduce je dostatočne podrobná. Namiesto toho sa chcem tu zamerať na malý detail, ktorý v dokumentácii chýba. Použitie kľúčov s reduce.

Reduce #

Z vyššie uvedenej oficiálnej dokumentácie vyzerá príklad pre reduce takto:

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

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

// 6

A skutočne, výpočet súčtu hodnôt je jedným z najpoužívanejších príkladov pre reduce. Je to doslova učebnicový príklad. Podobné príklady nájdete pravdepodobne aj pre iné jazyky.

Reduce so šípkovou funkciou #

Pre zlepšenie prepišme vyššie uvedené pomocou šípkovej funkcie, funkcie, ktorá bola pridaná do PHP ako súčasť anonymných funkcií. Sú dostupné aj v javascripte a rád ich tam používam, tak to skúsme:

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

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

// 6

Ušetrí to aj niekoľko stlačení kláves. V tejto chvíli by malo byť jasné aj začínajúcim Padawanom, že funkcia reduce ako prvý argument prijíma callback funkciu, ktorá má dva argumenty, $carry a samotný iterovaný $item, mnohokrát označovaný ako hodnota. Ak skutočne chceme iba vypočítať súčet hodnôt, toto je všetko, čo potrebujeme. Čo ale v situáciách, keď to nestačí?

Reduce s kľúčmi #

Predstavte si, že máme Collection miest a chceme pomocou reduce vypočítať celkovú vzdialenosť medzi nimi, len s reduce. Je to trochu zložité, pretože vzdialenosť je vzťah medzi dvomi mestami, takže musíme mať možnosť k nej pristúpiť v rámci callbacku. Keďže sa mi nepodarilo spoľahlivo nájsť dokumentáciu k tomu, rozhodol som sa to rýchlo zapísať, takže tu to je:

<?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;
});

Najdôležitejší detail tu je, že callback môže mať viac ako dva parametre, pričom tretí je $key. Taktiež musíme sprístupniť $cities v lokálnom rozsahu cez kľúčové slovo use a musíme skontrolovať, či sme nedosiahli koniec poľa.

Ukážte mi šípky #

Šípkové funkcie v javascripte môžu mať viacero príkazov. V PHP je povolený iba jeden výraz na šípkovú funkciu. Prepísanie vyššie uvedeného pomocou šípkovej funkcie je zložitejšie, ale možné.

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

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

Tu sa používa ternárny operátor. Je na čitateľovi, aby posúdil, či je to zlepšenie alebo zásah do čitateľnosti. Tiež sa zdá, že existuje príliš veľa spôsobov, ako ľudia preferujú formátovanie vyššie uvedeného kódu, takže niektorým môže pripadať strašidelný alebo škaredý. Pri šípkovej funkcii sú však vonkajšie premenné dostupné v lokálnom rozsahu. Teda $cities sú dostupné bez potreby kľúčového slova use.

Lepšie rozdeliť #

Použitie kľúčov s funkciou reduce, súčasťou Laravel Collections, môže byť užitočné v niektorých situáciách. Dokumentácia výslovne nespomína tretí parameter pre callback funkciu a keďže, ako bolo demonštrované vyššie, kód, ktorý ho využíva, nie je práve elegantný, možno je to vynechané z dobrého dôvodu.

Existuje iný spôsob? No, ako pri všetkom programovacom, odpoveď je áno. $key je vlastne len druhý atribút funkcie map a je spomínaný v dokumentácii, pozrite sa na to. V mnohých situáciách je použitie map s kľúčmi podobným spôsobom ako vyššie lepšie, pretože by nám umožnilo spustiť reduce na namapovaných hodnotách, napríklad vzdialenostiach. Vyžaduje si to dve funkcie namiesto jednej stručnej, ale výsledný kód môže byť explicitnejší. Pozrite nižšie:

<?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
);

Takto to rád píšem pri svojom aktuálnom štýle. Ako by ste to napísali vy?