La Torre di BaBasic - 1
La Torre di BaBasic - 1
Siamo ben lontani dal comprendere le regole che governano le lingue parlate dall'uomo, i cosiddetti linguaggi naturali; e non siamo neppure in grado di trattare compiutamente con strumenti matematici i ben più semplici linguaggi di programmazione che usiamo per dare istruzioni ai computer.
Tutti i linguaggi di programmazione, partendo da Fortran sino ad arrivare ad Ada, sono (se non proprio fratelli) perlomeno cugini: i tratti comuni sono parecchi. Le somiglianze sono dovute ad una semplice realtà: tutti i linguaggi sono programmi che traducono le istruzioni del programmatore in una forma comprensibile al computer e i linguaggi macchina dei computer sono tutti basati sulla stessa macchina fisica, l'architettura di Von Neumann.
Diventa dunque possibile, e interessante, trattare in una serie di articoli non un solo linguaggio di programmazione, ma una ampia famiglia, la cui scelta è limitata solo dallo spazio e dalle conoscenze di chi scrive. Non ci interessa minimamente la teoria matematica che sta dietro ai linguaggi: come abbiamo detto essa è incompleta (e, sia detto in confidenza, incredibilmente noiosa). Siamo invece interessati a scoprire somiglianze e differenze tra i linguaggi di programmazione: alla fine ci ritroveremo facilitati se vorremo imparare a programmare in un nuovo linguaggio; conosceremo per sommi capi le caratteristiche di un certo numero di linguaggi e potremo sceglierne uno a ragion veduta. Comprenderemo meglio cosa sia un linguaggio, e come esso funzioni. E pertanto saremo dei programmatori (della domenica o professionisti, non ha importanza) migliori.
La serie di articoli inaugurata dal brano che state leggendo sarà un appuntamento costante su queste pagine per tutto il 1988. Tratteremo regolarmente di Basic, Pascal, linguaggio C e Ada, ma ci occuperemo con frequenza anche di Lisp, Apl ed Assembly, e saltuariamente di altri ancora: se conoscete almeno uno dei primi quattro linguaggi non incontrete nessun problema nel seguire questa serie.
Nome | Anno | Scopo dichiarato |
---|---|---|
Assembler | 1950 |
|
Basic | 1965 | Facilitare l'apprendimento della programmazione) |
Lisp | 1964 | Fornire uno strumento slegato dalla natura fisica del computer |
C | 1972 | Permettere una programmazione evoluta ed efficiente |
Pascal | 1969 | Istruire i programmatori nelle tecniche di programmazione |
Ada | 1984 | Permettere la realizzazione di grandi progetti |
Durante la stesura di un programma, così come avviene quando dobbiamo risolvere un qualsiasi problema complesso, non ci sogneremo certo di poter trovare subito una soluzione completamente soddisfacente. Il programma viene scritto a pezzi, a blocchi ciascuno dei quali esegue una operazione moderatamente complessa. I pezzi di programma sono poi messi insieme come mattoncini in un gioco di costruzione realizzando il prodotto completo.
Ci sono molte ragioni che portano a scrivere un programma per pezzi: innanzitutto, un pezzo (una routine) può venire utilizzata in più punti dal programma.
E' inutile scrivere in più punti del programma uno stesso gruppo di istruzioni: perderemmo tempo e sprecheremmo spazio in memoria. Scrivere un programma in blocchi facilita la ricerca e correzione degli errori: se mi rendo conto che il programma funziona perfettamente ma non risponde alla richiesta di effettuare una stampa potrò, a colpo sicuro, cercare l'istruzione colpevole del difetto nella routine che gestisce la stampante. Ancora, una routine creata per consentire l'uso del mouse in un programma di gestione pizzerie sarà riutilizzabile con poche o nessuna modifica anche in un videogioco dove il mouse viene usato per guidare un bolide attraverso il solito labirinto.
Anche nel linguaggio macchina che il computer comprende direttamente sono state introdotte (dagli ingegneri che hanno progettato il microprocessore) delle istruzioni appositamente per facilitare la creazione di pezzetti di programma. I programmatori Assembler chiamano questi blocchi di istruzioni subroutine. Ecco un esempio:
MUSICHETTA | EQU | * | ;Subroutine che suona |
| LDX | #{BEGIN relazione_articoli_paragrafi} {testo} {rawHtml} {END relazione_articoli_paragrafi} | ; "O sole mio" |
|
|
| ;altre istruzioni... |
JSR MUSICHETTA ;Jump to SubRoutine
JSR MUSICHETTA ; due volte di fila Lo schema dovrebbe essere chiaro: la subroutine ha un nome (o un numero) di riferimento. E' composta da un certo numero di istruzioni e terminata dalla istruzione speciale...
RTS ;ritorno dalla subroutine.
Il programma che vuole usare la subroutine esegue l'istruzione...
JSR ;salta alla subroutine.
Durante l'esecuzione, quando il computer trova l'istruzione JSR salta alla routine e la esegue. Quando trova l'istruzione RTS ritorna al punto di partenza ed esegue l'istruzione successiva alla JSR. Nel linguaggio Basic troviamo una struttura simile: con l'istruzione GOSUB si comanda il salto alla subroutine, con la corrispettiva RETURN si termina la subroutine.
Nei linguaggi della famiglia di Algol, partendo la Pascal per arrivare sino ad Ada, viene sovrapposta a questa realtà fisica uno schema logico. A prezzo di un certo sforzo - compiuto dal creatore del compilatore - le routine guadagnano nuove caratteristiche, che le rendono più semplici da usare e più potenti. Le "routine più sofisticate" si chiamano procedure (quando non restituiscono risultati) e funzioni (quando lo fanno).
Una prima aggiunta riguarda la possibilità di avere variabili locali - di questo parleremo nel prossimo articolo; inoltre diviene possibile passare dei parametri alle routine e farsi restituire dei risultati.
Immaginiamo di aver scritto una complessa applicazione per il ministero delle finanze: una subroutine del programma deve saper risalire, dato un numero di codice fiscale, al nome del cittadino cui corrisponde il codice. Se il programma fosse scritto in Basic noi dovremmo scrivere
Print "Batti il codice fiscale "
Input CodFisc$
GoSub TrovaNome
Print "Il nome e': "; NomeTrovato$ Cioè dovremmo stabilire per convenzione che il codice fiscale in oggetto si trova nella variabile CodFisc$, che il risultato della ricerca si troverà in NomeTrovato$, e a queste convenzioni dovrebbero attenersi sia il programma che la routine. Una simile serie di convenzioni dovrebbe venire fissata per ogni routine: questo è faticoso e scomodo.
La funzione Pascal, invece, non obbliga a nessuna convenzione. Va definita e poi viene usata in modo analogo alle funzioni definite nel linguaggio. Noi scriveremmo:
Write ('Batti il codice fiscale ');
ReadLn (CodFisc);
NomeTrovato = TrovaNome (CodFisc); Nelle istruzioni che compongono la funzione si fa riferimento ad altre variabili, e non a CodFisc e NomeTrovato, ed il linguaggio si occupa dei problemi conseguenti al passaggio di valori avanti e indietro tra le variabili del programma e quelle interne della funzione.
Abbiamo menzionato poco fa la necessità di creare procedure riutilizzabili in futuri programmi: possiamo andare oltre e pensare a quanto sarebbe bello poter riutilizzare le procedure che abbiamo creato anche in situazioni differenti a quelle per cui sono state create, ma senza doverle modificare.
Entrambe le possibilità sono realtà nei linguaggi più moderni: diamo un'occhiata con maggior dettaglio di che si tratta.
Creando una serie di procedure e funzioni per il trattamento dei testi mentre creo un programma di word processing sono interessato alla possibilità di riutilizzarle in seguito quando dovr| creare un programma di analisi lessicale dei testi. Vediamo come questo problema pu| venire impostato in Ada.
Riunisco insieme le funzioni e procedure che ho creato, insieme alle variabili che usano ed ai tipi di dato dichiarati, in un package, un "pacco" cui do il nome di TextTools (perdonate la mia anglofilia, il nome significa "strumenti per il testo").
Package TextTools is
--dichiarazione delle strutture
--(procedure, funzioni, strutture dati)
--utilizzabili dai programmi che usano TextTools
Type BranoLetterario is private;
Function ContaSpaziInBrano (Oggetto: BranoLetterario)return integer;
Procedure TogliSpaziDiTroppo (Oggetto:BranoLetterario);
--eccetera eccetera
Package body TextTools is
--qui il codice delle procedure
Sono poi possibili due modalitý differenti per l'uso del package: a mo' di libreria, come dovrebbe essere nel caso dell'esempio, oppure per l'uso come sottoprogrammi di una sola applicazione. Il concetto di libreria Ë trovato anche in linguaggi meno potenti di Ada; ad esempio nel diffusissimo Ucsd Pascal la libreria Ë una delle aggiunte pi pregevoli. Con tutti i Pascal creati secondo quel progetto viene fornita una libreria simile, chiamata TurtleGraphics, che permette l'uso della grafica in modo relativamente omogeneo su svariati modelli di computer, un compito normalmente arduo date le innumerevoli differenze che esistono nel campo.
Se in Ucsd Pascal vogliamo creare una libreria come TurtleGraphics scriviamo:
Unit TurtleGraphics;
Interface
{Dichiarazione delle procedure richiamabili
dai programmi che usano la libreria}
Procedure InitTurtle;
Procedure SetColor (NewColor: Color);
Function WhereAmI: Point;
{ ...eccetera }
Possiamo usare la libreria così creata in un programma conuna dichiarazione di uso:
Uses TurtleGraphics;
Disgraziatamente, il concetto di libreria incoraggia la cosiddetta programmazione bottom up: cioé, avendo a disposizione un costrutto di tipo libreria, il programmatore è incoraggiato a scrivere dapprima le procedure e funzioni che usarà nel programma (la libreria) ed in seguito il programma che ne farà uso. Questo non solo risulta più naturale, ma è anche indispensabile perchè il compilatore Pascal Ucsd si rifiuterà di considerare il programma che usa una libreria inesistente (o non ancora completamente sviluppata, il che è lo stesso per quanto lo riguarda).
La limitazione è particolarmente sentita dai programmatori Pascal, che vengono incoraggiati sin dalla nascita a creare i loro programmi nel senso opposto, detto top down, sviluppando prima il programma, poi le funzioni e procedure più importanti, e via via scendendo sino a creare il codice che realizza i compiti più semplici.
Ada permette, come abbiamo detto, di creare anche dei package che non sono librerie. Anche per i non fanatici del metodo top down, questo sistema è inappagabile quando un gruppo di programmatori sta creando un gigantesco programma: il linguaggio Ada, al contrario di Pascal, permette di creare parti del programma senza che esista il codice di tutte le procedure che vengono usate. Il compilatore Ada richiede semplicemente che sia stata scritta la parte che Pascal chiama Interface, ed è in grado di risolvere tutti i riferimenti anche se la Implementation del package non è pronta.
Il compilatore Ada controlla poi, quando sia il package usato che il programma usante sono pronti, che le routine create siano coerenti con la dichiarazione che ne era stata fatta: in quel caso non richiede neppure che venga ricompilato il programma che usa quelle routine.
Come se tutto questo non fosse sufficiente, un Ada di recente manifattura controlla anche che il programmatore del package abbia commentato le procedure da lui create, descrivendo il loro uso e il loro scopo in una forma di metalinguaggio, e segnala errore in caso contrario. In tal modo, Ada garantisce che il programmatore-utilizzatore delle routine possa effettivamente fare uso del software, e non sia limitato dalla mancanza di una esauriente documentazione.
Torneremo su questi argomenti, e in particolare sulle capacità di Ada nel merito che non si fermano certo qui, in futuro, dopo aver parlato delle variabili - nella prossima puntata - e dei tipi di dato nella successiva.