Il codice che ci si trova è il più lontano possibile da quello funzionale. L'intera funzione è costruita attorno a un ciclo di effetti collaterali che muta un valore.
Ecco l'implementazione funzionale banale di un'operazione map
:
Array.prototype.map = function (fn) {
const [first, ...rest] = this;
return this.length === 0 ? [] : [fn(first)].concat(rest.map(fn));
};
Questo, tuttavia, farà esplodere lo stack per un array sufficientemente grande. Per evitare ciò, dobbiamo rendere la funzione ricorsiva in coda. Nella versione precedente, la coda è chiamata al metodo concat
. La chiamata verrà ottimizzata, ma questo non ci aiuta molto: vogliamo che la chiamata ricorsiva a map
sia la coda chiamata, in modo che it venga ottimizzato.
Il trucco standard per rendere una funzione ricorsiva in coda è introdurre un accumulatore che viene passato e quindi invertire il risultato.
Array.prototype.map = function (fn) {
const mapTailrec = (ary, acc) =>
ary.length === 0 ?
acc :
mapTailrec(ary.slice(0, -1), acc.concat([fn(ary[ary.length-1])]));
return mapTailrec(this, []).reverse();
};
[Nota: in realtà sto barando qui, perché Array.prototype.reverse
non è sfortunatamente trasparente referenziale. È è , tuttavia, è piuttosto semplice scrivere un% ca_de% di riferimento in modo ricorsivo in modo ricorsivo, quindi lascerò scorrere quella diapositiva.]
Come risulta, reverse
è un metodo generale di iterazione: tutto ciò che può essere fatto con i loop può essere fatto con le pieghe, e quindi ogni operazione di raccolta è in realtà solo un caso speciale di fold
:
Array.prototype.map = function (fn) {
return this.reduce([], (acc, el) => acc.concat([fn(el)]));
};