Da questo punto di vista i programmi eseguono una trasformazione fra i dati di ingresso e quelli di uscita secondo un modello che deriva direttamente dalla tesi di Church-Turing e che si concretizza nella morfologia della trasformazione sostenuta da Yourdon e Constantine, dove la decomposizione analitica dei programmi continua negli stessi termini fino ad arrivare ad un modulo centrale di trasformazione che riceve i dati d'ingresso da una gerarchia di moduli efferenti e produce i dati d'uscita per una gerarchia di moduli afferenti.
Non tutti i sistemi di elaborazione, comunque, rientrano facilmente in questo modello computazionale. A questo proposito si possono citare tre esempi per tutti: i sistemi operativi, i word processor e i sistemi a blackboard. In essi, la varietà e la discontinuità dei dati di ingresso costringe l'introduzione di una qualche sorta di parallelismo sulla decomposizione funzionale del sistema, nel senso che, ad ogni istante considerato, possono esserci diverse azioni parzialmente completate, alcune delle quali possono influenzarne o dipendere da altre.
Il tentativo che viene spesso fatto in questi casi è quello di caratterizzare tali sistemi più complessi come collezioni di programmi dotati di adeguate interfacce di comunicazione. Giudicando inappropriata la strutturazione gerarchica laddove l'organizzazione della computazione non lo richiede, Hoare ha rivendicato l'utilità generale di modelli non gerarchici per tutti quei sistemi dove le operazioni sui dati di ingresso e di uscita non sono semplici estensioni di un modello di programmazione ma ne rappresentano, piuttosto, le sue strutture fondamentali.
Ogni qualvolta si ha a che fare con sistemi i cui dati di ingresso non sono immediatamente prevedibili e molte loro combinazioni possono dar luogo a gravi malfunzionamenti del sistema stessa è senz'altro auspicabile riferirsi a programmi di controllo non strettamente gerarchici. Si pensi, fra l'altro, alla difficoltà di gestire condizioni di errore complesse in ambienti di programmazione sequenzialmente strutturati (un esempio, per tutti, il PASCAL).
Ciò che differenzia profondamente un sistema reattivo da un sistema gerarchico è il tipo di decomposizione funzionale che viene adottata nelle realizzazione del comportamento globale del sistema rispetto ai dati di ingresso/uscita. Il ruolo fondamentale giocato dall'interconnessione fra programma e mondo esterno in termini di informazioni acquisite e azioni effettuate fa si che il sistema venga decomposto verticalmente in moduli di competenza, piuttosto che orizzontalmente in moduli funzionali.
La decomposizione orizzontale è quella che si realizza, ad esempio, nella shell di UNIX con la pipe |, per interconnettere fra loro i comandi di shell in modo che l'output prodotto dal primo diventi input per il secondo, con uno schema generale del tipo S1 | S2, essendo S1 e S2 due comandi arbitrari.
Gli esempi più numerosi e, di più semplice comprensione si hanno, comunque, nell'ambito della programamzione strutturata sequenziale allorchè si organizza la computazione per livelli di astrazione, dove a ciascun livello resta associato un tipo astratto di dati ed, inoltre, la comunicazione col mondo esterno avviene solamente attraverso il livello più alto. A questo proposito si può citare il caso dei compilatori che nelle quattro fasi di analisi lessicale, analisi sintattica, generazione del codice e ottimizzazione definiscono quattro livelli distinti, il primo dei quali riceve come dati di ingresso la sequenza dei caratteri che rappresenta il programma sorgente da compilare, mentre l'ultimo produce come uscita la sequenza di byte che rappresenta il risultato della comunicazione. I livelli intermedi non comunicano con l'esterno ma soltanto con quelli immediatamente adiacenti.
Possiamo dire, a questo punto, che la struttura gerarchica dei programmi si presenta nella classica forma a pipeline dove ciascun modulo funzionale consuma i dati prodotti dal modulo precedente e produce i dati per il modulo seguente. Il primo elemento della catena riceve i dati in ingresso, mentre l'ultimo produce i dati di uscita. Ogni modulo corrisponde ad uno specifico livello di astrazione dell'informazione portata dai dati (provenienti dall'esterno) e si riflette sulla rappresentazione dell'informazione adottata per quel livello.
Ad esempio, nel caso del compilatore, il modulo di analisi lessicale aggrega la sequenza di caratteri del programma sorgente in token, dove ciascuno rappresenta una parola del vocabolario del linguaggio con l'informazione supplementare della categoria grammaticale di appartenenza. Il modulo di analisi sintattica, invece, produce e manipola alberi sintattici che sono alla base del processo di traduzione.
Usando il modello di interazione produttore/consumatore lo schema implementativo del pipeline consente di introdurre nei programmi gerarchici strettamente sequenziali una certa dose di parallelismo, avvalendosi del fatto che, in generale, un modulo funzionale non necessita di tutto il proprio input prima di iniziare a produrre il corrispondente output.
Concettualmente (ma, assai spesso, praticamente) fra due moduli adiacenti è definita un'interfaccia il cui compito è quello di sostenere il transito di dati, i quali subiscono un vero e proprio filtraggio passando da un modulo al successivo, secondo quanto indicato nella figura 1.
fig. 1 : Pipeline di Moduli Funzionali
Chiaramente è assolutamente arbitrario il modo con cui ciascun modulo è effettivamente realizzato, purchè il suo comportamento sia tale da produrre in uscita i dati con le richieste caratteristiche di manipolazione rispetto ai dati di ingresso considerati. Più specificatamente, è necessario definire come devono essere strutturati gli insiemi dei valori di ingresso e di uscita e quale deve essere il tipo di trasformazione da effettuare su di essi.
Fra tutte le possibili implementazioni del modulo possiamo scegliere, in particolare, quella la cui struttura dati interna rappresenta la storia evolutiva del modulo rispetto al flusso dei dati in ingresso. In questo senso l'organizzazione del modulo viene definita in termini di una funzione di transizione di stato f la quale, in base allo stato corrente del modulo e al particolare dato d'ingresso considerato, deve stabilire univocamente il nuovo stato del modulo stesso. Inoltre, per ogni stato, deve essere specificato il dato di uscita che il modulo deve produrre, definendo quella che si chiama la trasformazione di uscita g. La figura 2 ne dà una rappresentazione grafica appropriata.
fig. 2 : Agente Computazionale
Ogniqualvolta l'implementazione di un modulo funzionale è realizzata in modo tale da metterne in evidenza le caratteristiche di interazione con i dati di ingresso e associarne la loro manipolazione e la produzione dei dati di uscita con la propria evoluzione, diremo che il modulo rappresenta un agente computazionale.
Le relazioni seguenti
x[n+1] = f( u[n], x[n] )
y[n] = g( x[n])
formalizzano il concetto di agente computazionale.