Se si creano da zero le operazioni minime di un computer generico, "Iteration" viene prima come blocco predefinito e richiede meno risorse rispetto alla "ricorsione", ergo è più veloce.
Stabiliremo una gerarchia di concetti, partendo da zero e definendo in primo luogo i concetti di base e fondamentali, quindi costruiremo concetti di secondo livello con quelli e così via.
Primo concetto: celle di memoria, memoria, stato . Per fare qualcosa hai bisogno di luoghi in cui archiviare i valori dei risultati finali e intermedi. Supponiamo di avere un array infinito di celle "intere", chiamate Memory , M [0..Infinite].
Istruzioni: fai qualcosa - trasforma una cella, cambia il suo valore. alter state . Ogni istruzione interessante esegue una trasformazione. Le istruzioni di base sono:
a) Impostare e spostare le celle di memoria
- memorizzare un valore in memoria, ad esempio: memorizzare 5 m [4]
- copiare un valore in un'altra posizione: ad esempio: store m [4] m [8]
b) Logica e aritmetica
- e, o, xor, no
- aggiungi, sub, mul, div. es. aggiungi m [7] m [8]
Un agente esecutivo : un nucleo in una CPU moderna. Un "agente" è qualcosa che può eseguire istruzioni. Un agente può anche essere una persona che segue l'algoritmo su carta.
Ordine dei passaggi: una sequenza di istruzioni : vale a dire: fare prima questo, farlo dopo, ecc. Una sequenza imperativa di istruzioni. Anche le espressioni di una riga sono "una sequenza imperativa di istruzioni". Se hai un'espressione con un "ordine di valutazione" specifico, allora hai dei passaggi . Significa che anche una singola espressione composta ha "passi" impliciti e ha anche una variabile locale implicita (chiamiamola "risultato"). per esempio:
4 + 3 * 2 - 5
(- (+ (* 3 2) 4 ) 5)
(sub (add (mul 3 2) 4 ) 5)
L'espressione sopra implica 3 passaggi con una variabile "risultato" implicita.
// pseudocode
1. result = (mul 3 2)
2. result = (add 4 result)
3. result = (sub result 5)
Quindi anche le espressioni infix, dato che hai un ordine di valutazione specifico, sono una sequenza imperativa di istruzioni . L'espressione implica una sequenza di operazioni da eseguire in un ordine specifico e, poiché vi sono passaggi , esiste anche una variabile intermedia "risultato" implicita.
Puntatore istruzioni : se si dispone di una sequenza di passaggi, è presente anche un "puntatore istruzioni" implicito. Il puntatore dell'istruzione segna l'istruzione successiva e avanza dopo che l'istruzione è stata letta ma prima che l'istruzione venga eseguita.
In questa macchina pseudo-informatica, il puntatore a istruzioni fa parte della memoria . (Nota: normalmente il puntatore di istruzioni sarà un "registro speciale" in un core della CPU, ma qui semplificheremo i concetti e assumeremo che tutti i dati (registri inclusi) facciano parte di "Memoria")
Salta : una volta che hai un numero ordinato di passaggi e un puntatore a istruzioni , puoi applicare l' istruzione " store " per modificare il valore del puntatore a istruzioni stesso. Chiameremo questo uso specifico delle istruzioni del negozio con un nuovo nome: Salta . Usiamo un nuovo nome perché è più facile pensarlo come un nuovo concetto. Modificando il puntatore alle istruzioni stiamo dicendo all'agente di "andare al passaggio x".
Iterazione infinita : saltando indietro, ora puoi far "ripetere" all'agente un certo numero di passaggi. A questo punto abbiamo Iterazione infinita.
1. mov 1000 m[30]
2. sub m[30] 1
3. jmp-to 2 // infinite loop
Condizionale : esecuzione condizionale delle istruzioni. Con la clausola "condizionale", è possibile eseguire in modo condizionale una delle numerose istruzioni basate sullo stato corrente (che può essere impostato con un'istruzione precedente).
Iterazione corretta : ora con la clausola condizionale , possiamo sfuggire al ciclo infinito dell'istruzione jump back . Ora abbiamo un ciclo condizionale e quindi una corretta Iterazione
1. mov 1000 m[30]
2. sub m[30] 1
3. (if not-zero) jump 2 // jump only if the previous
// sub instruction did not result in 0
// this loop will be repeated 1000 times
// here we have proper ***iteration***, a conditional loop.
Denominazione : assegnazione di nomi a una specifica posizione di memoria contenente dati o trattenendo un passaggio . Questa è solo una "comodità" da avere. Non aggiungiamo nuove istruzioni avendo la capacità di definire "nomi" per le posizioni di memoria. La "denominazione" non è un'istruzione per l'agente, è solo una comodità per noi. La denominazione rende il codice (a questo punto) più facile da leggere e più facile da modificare.
#define counter m[30] // name a memory location
mov 1000 counter
loop: // name a instruction pointer location
sub counter 1
(if not-zero) jmp-to loop
Sottoprogramma di un livello : supponiamo che ci sia una serie di passaggi che devi eseguire frequentemente. È possibile memorizzare i passaggi in una posizione denominata in memoria e quindi saltare a quella posizione quando è necessario eseguirli (chiamata). Alla fine della sequenza dovrai tornare al punto di chiamata per continuare l'esecuzione. Con questo meccanismo, stai creando nuove istruzioni (subroutine) componendo le istruzioni di base.
Implementazione: (non sono richiesti nuovi concetti)
- Conservare il puntatore di istruzioni corrente in una posizione di memoria predefinita
- salta alla subroutine
- al termine della subroutine, si recupera il puntatore istruzioni dalla posizione di memoria predefinita, tornando effettivamente alle seguenti istruzioni della chiamata originale
Problema con l' implementazione a un livello : non è possibile chiamare un'altra subroutine da una subroutine. In tal caso, sovrascriverai l'indirizzo di ritorno (variabile globale), quindi non puoi nidificare le chiamate.
Per implementare meglio le subroutine: è necessario uno STACK
Stack : definisci uno spazio di memoria che funzioni come "stack", puoi "spingere" i valori nello stack e anche "pop" l'ultimo valore "spinto". Per implementare uno stack avrai bisogno di uno Stack Pointer (simile all'Istruttore Pointer) che punta alla vera "testa" dello stack. Quando si "spinge" un valore, il puntatore dello stack diminuisce e si memorizza il valore. Quando si "pop", si ottiene il valore dal puntatore dello stack effettivo e quindi il puntatore dello stack viene incrementato.
Subroutine Ora che abbiamo uno stack possiamo implementare subroutine appropriate che consentano chiamate nidificate . L'implementazione è simile, ma invece di memorizzare il puntatore di istruzioni in una posizione di memoria predefinita, "spingiamo" il valore dell'IP nello stack . Alla fine della subroutine, abbiamo semplicemente "pop" il valore dallo stack, saltando effettivamente indietro all'istruzione dopo la chiamata originale . Questa implementazione, con uno "stack", consente di chiamare una subroutine da un'altra subroutine. Con questa implementazione possiamo creare diversi livelli di astrazione quando definiamo nuove istruzioni come subroutine, usando le istruzioni di base o altre subroutine come elementi costitutivi.
Ricorsione : cosa succede quando una subroutine si chiama? Questo si chiama "ricorsione".
Problema: sovrascrivendo i risultati intermedi locali è possibile memorizzare in memoria un sottoprogramma. Poiché chiamate / riutilizzate gli stessi passaggi, se il risultato intermedio viene archiviato in posizioni di memoria predefinite (variabili globali), verranno sovrascritte sulle chiamate nidificate.
Soluzione: per consentire la ricorsione, le subroutine dovrebbero archiviare i risultati intermedi locali nello stack , pertanto, su ogni chiamata ricorsiva (diretta o indiretta) i risultati intermedi vengono memorizzati in posizioni di memoria diverse.
...
Infine, nota che hai molte opportunità di usare la ricorsione. Hai strutture dati ricorsive ovunque, ne stai osservando una adesso: parti del DOM che supportano ciò che stai leggendo sono un RDS, un'espressione JSON è un RDS, il file system gerarchico nel tuo computer è un RDS, ovvero: hai una directory radice, contenente file e directory, ogni directory contenente file e directory, ognuna di quelle directory contenenti file e directory ...