Funkcionálně v JavaScriptu
|
Aktuálně dělám na jednom projektu v JavaScriptu. Je to UI komponenta, kde se pracuje s poměrně velkým množstvím dat. Na vstupu a na výstupu je JSON. Tudíž je to vlastně funkce transformující data. Jak jinak k tomu problému přistoupit než funkcionálně?
Data jsou uložena v persistentních datových struktůrách a mají zhruba následující strukturu:
var data = Immutable.fromJS({
foos: {'foo-id': {id: 'foo-id'}},
bars: {'bar-id': {id: 'bar-id', foo: 'foo-id'}}
});
Prvky jsou indexovány podle typu a dále podle jejich uníkátních id
. Druhý typ prvků má vazbu na ten první. Vazba není referencí, ale pomocí id
a navázený prvek se dá snadno vytáhnout pomocí funkce getIn
, která umí efektivně procházet stromovou strukturu.
var bar = data.getIn(['bars', 'bar-id']);
var foo = data.getIn(['foos', bar.get('foo')]);
Dnes jsme narazil na zajímavý problém. Potřebuji mazat záznamy a když smažu foo
potřebuju smazat i patřičné bar
y, které na něj mají referenci, protože jejich přítomnost už není žádoucí. Smazat samotné foo
je celkem přímočaré:
var nextData = data.removeIn(['foos', id]);
Funkce removeIn
vrací novou koleci bez prvku se zadanou cestou. Teď ovšem potřebujeme najít všechny bar
y, které mají vazbu na námi mazané foo
. Pro definici dataflow používám transducery:
var xform = comp(
filter(x => x.get('foo') == id),
map(x => x.get('id')));
Celkem jednoduchý, přímočarý kód. Nic extra. Ale co teď s nima? Potřebujeme z immutable kolekce odebrat N prvků. Mohli bychom to vzít povědomou imperativní cestou:
var nextData = data.removeIn(['foos', id]);
into([], xform, data.get('bars').values()).forEach(x => {
nextData = nextData.removeIn(['bars', x]);
});
Ale opravdu používáme immutabilní datové struktury, abychom vzápětí dělali takový prasárničky? Já tedy ne. Vyjdeme ze znalosti, že removeIn
vrací novou kolekci. Tudíž by se nám hodila nějaká agregace. To není nic jinýho než využití transduce:
var nextData = data.withMutations(d =>
transduce(
xform,
(acc, x) => acc.removeIn(['bars', x]),
d.removeIn(['foos', id]),
data.get('bars').values()));
Funkce withMutations
nám umožňuje efektivně (díky transient kolekcím) a bezpečně provést změny pro dosažení výsledného stavu. Nedochází ke změně původní kolekce, ani nám měněný stav nikam neutíká mimo scope.
Funkce transduce
vezme již vytvořený transducer pro nalezení id
prvků k odebrání. Pak definujeme krok redukce, tj. vezmeme kolekci z předchozího kroku a odebereme z ní jeden prvek, jehož id
zrovna máme. Následuje počáteční hodnota redukce, tou tedy je kolekce po odebrání foo
. A nakonec data, nad kterými to celé probíhá a to jsou jednotlivé bar
y, kterými krmíme transducer xform
, z kterého lezou id
čka pro step function. A tím se kruh uzavřel.
Tak a teď se jděte projít.