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?