Compre... cože???
Když se učíte Elixir, jednou narazíte na pojem Comprehensions.
Říkáte si, co to do pr…. je??? Pak uvidíte klíčové slovo for
a
zaradujete se. Jasně, starý dobrý cyklus, jen mu funkcionální zaklínači
říkají jinak, aby byli zajímaví. Nenechte se ale zmást prvním dojmem!
Jde o odlišný koncept.
Není to ovšem nic, co už dávno neznáte, ačkoliv to možná není na první
pohled vidět. Pokud znáte SELECT
z SQL aspoň z rychlíku, asi pro vás
není problém dešifrovat následující výraz:
SELECT o.jmeno FROM osoby AS o WHERE o.vek >= 18;
A teď Elixir:
jmena_plnoletych = for o <- osoby, o.vek >= 18, do: o.jmeno
Výsledkem je seznam jmen plnoletých osob, uložený v proměnné jmena_plnoletych
.
Pro úplnost - v případě SQL předpokládám tabulku osoby
se sloupci jmeno
a vek
,
v případě Elixir proměnnou osoby
s obsahem např.:
[
%{jmeno: "Viktor", vek: 27},
%{jmeno: "Monika", vek: 23},
%{jmeno: "Kája", vek: 12},
... atd ...
]
Je nutné si uvědomit, že comprehension, stejně jako SELECT
, vrací seznam. Není
to tedy smyčka, která něco dělá, ale výraz, který transformuje data. V příkladu
výše transformuje seznam osob na seznam jmen osob plnoletých. Kromě transformace
může tedy data zároveň filtrovat (plnoleté osoby). To však není vše. Co třeba
výraz:
SELECT o.jmeno, m.okres FROM osoby AS o
INNER JOIN mesta AS m ON o.mesto = m.nazev
WHERE o.vek >= 18
A podobně v Elixiru:
vysledek =
for o <- osoby, m <- mesta, o.mesto == m.nazev, o.vek >= 18 do
{o.jmeno, m.okres}
end
Comprehension může tedy i kombinovat data z více zdrojů, stejně jako JOIN v SQL.
Příklad výše uloží do proměnné vysledek
seznam tuplů jmen plnoletých osob a okresu,
ve kterém žijí. Opět pro úplnost - zde předpokládám, že v SQL tabulce osoby
existuje navíc
sloupec mesto
obsahující název města, kde osoba žije, a existenci tabulky mesta
se sloupci nazev
a okres
. Pro Elixir pak proměnné:
osoby = [%{jmeno: "Viktor", vek: 27, mesto: "Turnov"}, ... atd ... ]
mesta = [%{nazev: "Turnov", okres: "Semily"}, ... atd ... ]
V Elixiru lze comprehensions využít například i k agregaci. Následující příklad vytváří mapu obsahující seznam jmen plnoletých osob v jednotlivých okresech:
osoby = [
%{jmeno: "Viktor", vek: 27, mesto: "Turnov"},
%{jmeno: "Monika", vek: 23, mesto: "Jilemnice"},
%{jmeno: "Kája", vek: 12, mesto: "Neratovice"},
%{jmeno: "Albert", vek: 35, mesto: "Kralupy nad Vltavou"}
]
mesta = [
%{nazev: "Turnov", okres: "Semily"},
%{nazev: "Jilemnice", okres: "Semily"},
%{nazev: "Neratovice", okres: "Mělník"},
%{nazev: "Kralupy nad Vltavou", okres: "Mělník"},
]
vysledek =
for o <- osoby,
m <- mesta,
o.mesto == m.nazev,
o.vek >= 18,
reduce: %{} do
okresy ->
Map.update(okresy, m.okres, [o.jmeno], fn
jmena_v_okrese -> [o.jmeno | jmena_v_okrese]
end)
end
Proměnná vysledek
pak obsahuje:
%{
"Mělník" => ["Albert"],
"Semily" => ["Monika", "Viktor"]
}
V SQL pro stejný výsledek použiji GROUP BY
:
SELECT m.okres, array_agg(o.jmeno) FROM osoby AS o
INNER JOIN mesta AS m ON o.mesto = m.nazev
WHERE o.vek >= 18
GROUP BY m.okres
Kromě reduce
lze v comprehensions
použít i další přepínače, jako into
a uniq
, filtrování pomocí pattern-matchingu a jiné
vychytávky (více v dokumentaci).
Nyní si můžete říct: Vždyť elixirový for
je vlastně SELECT
! Není - ale jde o příbuzný
koncept. Až tedy v Elixiru narazíte na for
, nenechte se zmást a místo cyklu si raději
vzpomeňte na SQL.
Pozn. 1: Samozřejmě výše uvedené ukázky nejsou vždy nejlepším řešením problému,
jde pouze o demonstraci for
. Ale pro jednorázové sloučení dat například ze dvou
CSV souborů obvykle není potřeba vymýšlet nic
složitějšího, než “joinování” pomocí comprehensions.
Pozn. 2: Elixirové ukázky v podobě Livebooku jsou ke stažení zde.