49 Iterators e iterbile

ìUn esempio di collezioni built-in in js sono le stringhe e gli array altre aggiunte dopo come mappe e set. È sempre apprezzabile il meccanismo che serve ad elaborarli in sequenza ai fini di diverse operazioni.

Quando una collezione offre un supporto per la scansione degli oggetti che contiene, si dice essere iterabile o in inglese iterable. Ciò che a livello linguistico permette di effettuare la scansione è chiamato iteratoreiterator. Gli iterators sono particolarmente interessanti perché esistono dei costrutti linguistici in grado di sfruttarli facilmente, vediamo un esempio:

let s="ciao";
for (carattere of s) writeln(carattere);

la stringa ciao è una collezione di caratteri che offre un iteratore tramite costrutti specifici tipo questo for

for ( of )

la riga

for (carattere of s) writeln(carattere);

va letta: per (for) ogni elemento (carattere) della collezione s , stampalo writeln(carattere).

Questo costrutto ha diversi vantaggi su uno tradizionale:

for (let i=0; i<s.length; i++) writeln(s[i]);

Intanto è più leggibile, più intuitivo, inoltre posso commettere più errori col tradizionale, devo assegnare un contatore, devo utilizzare il metodo lenght, fare il controllo sugli elementi, devo settore l’incremento, utilizzare gli array nella stampa con la loro notazione.

È interessante capire come funziona l’iteratole dietro le quinte

for (carattere of s) writeln(carattere);

Vengono utilizzati sicuramente i simboli visti nella lezione precedente, di cui js ne prevede alcuni predefiniti, conosciuti come well know symbols o simboli noti. Quello che rappresenta il punto di ingresso per un iteratore  è rappresentato da

Symbol.iterator

memorizzato nella struttura stessa di una collezione come le stringhe e rappresenta come proprietà una funzione che possiamo richiamare per far generare un iteratore per quella collezione.

writeln( typeof(s[Symbol.iterator]) );

a prova di ciò la stampa sul tipo ( typeof ) della proprietà iterator dell’oggetto s è function. Questa non è l’iterator, ma è la funzione che se chiamata restituisce l’iterator.

Memorizziamo in una variabile il risultato dell’invocazione della funzione s[Symbol.iterator]() .

let iteratore = s[Symbol.iterator]();
writeln( typeof(iteratore) );

stampando iteratore otterremo Object che l’iteratole legato strettamente alla stringa s dalla quale siamo partiti, ed espone un metodo pubblico chiamato next() che ci fornice l’elemento successivo di quella collezione (restituisce il successivo carattere)

let elemento = iteratore.next();

Il metodo next() restituisce un oggetto formato da 2 elementi, il primo è un boolean denominato done, che verifica se abbiamo raggiunto la fine degli elementi

writeln(elemento.done);

all’inizio siamo sul carattere c della stringa ciao, quindi done sarà FALSE.

L’altro elemento è il valore estratto catturato in value

writeln(elemento.value);

il primo value di s stamperà il carattere c, il secondo i, il terzo a… Eseguendo 5 volte

elemento = iteratore.next();
writeln(elemento.done);
writeln(elemento.value);

alla quinta otterrò che il done sarà FALSE mentre value UNDEFINED perchè abbiamo consumato tutti gli elementi della collezione. Tutto questo meccanismo è riassunto comodamente in for (of).

Per protezione le proprietà predefinite degli iterativi noti sono solo leggibili e non possono essere modificate, infatti seppur non ottenga errori, nel tentativo azzardato di modifica, non apporto cambiamenti e la stampa risulta uguale alla prima

String[Symbol.iterator] = function () {}
for (carattere of s) writeln(carattere);