|
|
| << | < | > | >> |IndicePrefazione xiii Introduzione xv 1 Macchine astratte 1 1.1 La nozione di macchina astratta e l'interprete 1 1.1.1 Interprete 3 1.1.2 Un esempio di macchina astratta: la macchina hardware 6 1.2 Implementazione di un linguaggio 9 1.2.1 Realizzazione di una macchina astratta 10 1.2.2 Implementazione: il caso ideale 13 1.2.3 Implementazione: il caso reale e la macchina intermedia 18 1.3 Gerarchie di macchine astratte 21 1.4 Sommario del capitolo 24 1.5 Nota bibliografica 25 1.6 Esercizi 25 2 Descrivere un linguaggio di programmazione 27 2.1 Livelli di descrizione 27 2.2 Grammatica e sintassi 28 2.2.1 Grammatiche libere 30 2.3 Vincoli sintattici contestuali 40 2.4 Compilatori 42 2.5 Semantica 47 2.6 Pragmatica 54 2.7 Implementazione 55 2.8 Sommario del capitolo 55 2.9 Nota bibliografica 55 2.10 Esercizi 56 3 Fondamenti 57 3.1 Il problema della fermata 57 3.2 Espressività dei linguaggi di programmazione 59 3.2.1 Formalismi per la calcolabilità 60 3.3 Esistono più funzioni che algoritmi 62 3.4 Sommario del capitolo 64 3.5 Nota bibliografica 64 3.6 Esercizi 64 4 I nomi e l'ambiente 67 4.1 Nomi e oggetti denotabili 67 4.1.1 Oggetti denotabili 69 4.2 Ambiente e blocchi 70 4.2.1 I blocchi 72 4.2.2 Tipi di ambiente 72 4.2.3 Operazioni sull'ambiente 75 4.3 Regole di scope 78 4.3.1 Scope statico 79 4.3.2 Scope dinamico 81 4.3.3 Alcuni problemi di scope 84 4.4 Sommario del capitolo 87 4.5 Nota bibliografica 88 4.6 Esercizi 88 5 La gestione della memoria 93 5.1 Tecniche di gestione della memoria 93 5.2 Gestione statica della memoria 95 5.3 Gestione dinamica mediante pila 96 5.3.1 Record di attivazione per i blocchi in-line 98 5.3.2 Record di attivazione per le procedure 100 5.3.3 Gestione della pila 102 5.4 Gestione dinamica mediante heap 104 5.4.1 Blocchi di dimensione fissa 105 5.4.2 Blocchi di dimensione variabile 106 5.5 Implementazione delle regole di scope 109 5.5.1 Scope statico: la catena statica 109 5.5.2 Scope statico: il display 114 5.5.3 Scope dinamico: lista di associazioni e CRT 116 5.6 Sommario del capitolo 121 5.7 Nota bibliografica 121 5.8 Esercizi 122 6 Strutturare il controllo 125 6.1 Le espressioni 125 6.1.1 Sintassi delle espressioni 126 6.1.2 Semantica delle espressioni 129 6.1.3 Valutazione delle espressioni 131 6.2 La nozione di comando 136 6.2.1 La variabile 137 6.2.2 L'assegnamento 138 6.3 Comandi per il controllo di sequenza 142 6.3.1 Comandi per il controllo di sequenza esplicito 143 6.3.2 Comandi condizionali 146 6.3.3 Comandi iterativi 150 6.4 Programmazione strutturata 157 6.5 La ricorsione 159 6.5.1 La ricorsione in coda 162 6.5.2 Ricorsione o iterazione? 167 6.6 Sommario del capitolo 168 6.7 Nota bibliografica 168 6.8 Esercizi 169 7 Astrarre sul controllo 171 7.1 Sottoprogrammi 172 7.1.1 Astrazione funzionale 174 7.1.2 Passaggio dei parametri 175 7.2 Funzioni di ordine superiore 186 7.2.1 Funzioni come parametro 187 7.2.2 Funzioni come risultato 193 7.3 Eccezioni 195 7.3.1 Implementare le eccezioni 200 7.4 Sommario del capitolo 202 7.5 Nota bibliografica 203 7.6 Esercizi 204 8 Strutturare i dati 207 8.1 Tipi di dato 207 8.1.1 Tipi come supporto all'organizzazione concettuale 208 8.1.2 Tipi per la correttezza 209 8.1.3 Tipi e implementazione 210 8.2 Sistemi di tipi 211 8.2.1 Controlli statici e dinamici 212 8.3 Tipi scalari 214 8.3.1 Booleani 215 8.3.2 Caratteri 215 8.3.3 Interi 215 8.3.4 Reali 216 8.3.5 Virgola fissa 216 8.3.6 Complessi 216 8.3.7 Void 217 8.3.8 Enumerazioni 217 8.3.9 Intervalli 218 8.3.10 Tipi ordinali 219 8.4 Tipi composti 219 8.4.1 Record 219 8.4.2 Record varianti e unioni 221 8.4.3 Array 226 8.4.4 Insiemi 232 8.4.5 Puntatori 232 8.4.6 Tipi ricorsivi 238 8.4.7 Funzioni 240 8.5 Equivalenza 241 8.5.1 Equivalenza per nome 242 8.5.2 Equivalenza strutturale 242 8.6 Compatibilità e conversione 245 8.7 Polimorfismo 248 8.7.1 Overloading 249 8.7.2 Polimorfismo universale parametrico 250 8.7.3 Polimorfismo universale di sottotipo 252 8.7.4 Cenni sull'implementazione 254 8.8 Controllo e inferenza di tipo 255 8.9 Sicurezza: un bilancio 257 8.10 Evitare i dangling reference 258 8.10.1 Tombstone 258 8.10.2 Lucchetti e chiavi 260 8.11 Garbage collection 260 8.11.1 Contatori dei riferimenti 263 8.11.2 Mark and sweep 264 8.11.3 Intermezzo: rovesciare i puntatori 265 8.11.4 Mark and compact 267 8.11.5 Copia 268 8.12 Sommario del capitolo 270 8.13 Nota bibliografica 271 8.14 Esercizi 271 9 Astrarre sui dati 275 9.1 Tipi di dato astratti 276 9.2 Nascondere l'informazione 279 9.2.1 Indipendenza dalla rappresentazione 282 9.3 Moduli 282 9.4 Sommario del capitolo 284 9.5 Nota bibliografica 286 9.6 Esercizi 286 10 Il paradigma orientato agli oggetti 287 10.1 Limiti dei tipi di dato astratti 287 10.1.1 Un primo bilancio 291 10.2 Concetti fondamentali 291 10.2.1 Oggetti 292 10.2.2 Classi 294 10.2.3 Incapsulamento 296 10.2.4 Sottotipi 298 10.2.5 Ereditarietà 303 10.2.6 Selezione dinamica dei metodi 308 10.3 Aspetti implementativi 312 10.3.1 Ereditarietà singola 314 10.3.2 Il problema della classe base fragile 316 10.3.3 Selezione dinamica dei metodi nella JVM 317 10.3.4 Ereditarietà multipla 320 10.4 Polimorfismo e generici 326 10.4.1 Polimorfismo di sottotipo 326 10.4.2 Generici in Java 329 10.4.3 Implementazione dei generici in Java 333 10.4.4 Generici, array e gerarchia di sottotipo 334 10.4.5 Overriding covarianti e controvarianti 336 10.5 Sommario del capitolo 339 10.6 Nota bibliografica 340 10.7 Esercizi 340 11 Il paradigma funzionale 343 11.1 Computazioni senza stato 343 11.1.1 Espressioni e funzioni 345 11.1.2 Computazione come riduzione 347 11.1.3 Gli ingredienti fondamentali 347 11.2 Valutazione 349 11.2.1 Valori 350 11.2.2 Sostituzione senza cattura 350 11.2.3 Strategie di valutazione 351 11.2.4 Confronto tra strategie 353 11.3 Programmare in un linguaggio funzionale 355 11.3.1 Ambiente locale 355 11.3.2 Interattività 355 11.3.3 Tipi 356 11.3.4 Pattern matching 357 11.3.5 Oggetti infiniti 359 11.3.6 Aspetti imperativi 360 11.4 Implementazione: la macchina SECD 362 11.5 Il paradigma funzionale a confronto 364 11.6 Fondamenti: A-calcolo 367 11.7 Sommario del capitolo 374 11.8 Nota bibliografica 374 11.9 Esercizi 375 12 Il paradigma logico 377 12.1 Deduzione come computazione 377 12.1.1 Un esempio 378 12.2 Sintassi 382 12.2.1 Il linguaggio della logica del prim'ordine 382 12.2.2 I programmi logici 384 12.3 Teoria dell'unificazione 385 12.3.1 La variabile logica 386 12.3.2 Sostituzione 387 12.3.3 L'unificatore più generale 390 12.3.4 Un algoritmo di unificazione 392 12.4 Il modello computazionale 395 12.4.1 L'universo di Herbrand 395 12.4.2 Interpretazione dichiarativa e procedurale 396 12.4.3 La chiamata di procedura 397 12.4.4 Controllo: il non determinismo 401 12.4.5 Alcuni esempi 404 12.5 Estensioni 407 12.5.1 PROLOG 407 12.5.2 Programmazione logica e basi di dati 411 12.5.3 Programmazione logica con vincoli 413 12.6 Pregi e difetti del paradigma logico 415 12.7 Sommario del capitolo 417 12.8 Nota bibliografica 418 12.9 Esercizi 418 13 Una breve panoramica storica 421 13.1 Gli inizi 421 13.2 Fattori evolutivi dei linguaggi 424 13.3 Anni '50 e '60 425 13.4 Anni '70 429 13.5 Anni '80 434 13.6 Anni '90 437 13.7 Sommario del capitolo 440 13.8 Nota bibliografica 440 Bibliografia 443 Indice analitico 449 |
| << | < | > | >> |Pagina xiiiPrefazioneHo accettato con grande piacere l'invito rivoltomi a scrivere queste poche righe di prefazione per almeno due ragioni. La prima è che la richiesta mi viene da due colleghi che ho sempre tenuto in grandissima considerazione, fin dal tempo in cui li ho potuti conoscere ed apprezzare come studenti e come giovani ricercatori. La seconda ragione è che il testo di Gabbrielli e Martini è molto vicino al libro che io avrei sempre voluto scrivere e, per ragioni varie, non ho mai scritto. In particolare, l'approccio seguito dal libro è quello che io stesso ho seguito nell'organizzazione di vari corsi sui linguaggi di programmazione che ho insegnato, in diverse fasi e sotto diverse etichette, per quasi trent'anni. L'approccio, schematizzato in due parole, è quello di introdurre i concetti generali (sia dei meccanismi linguistici che delle corrispondenti strutture di implementazione) in modo indipendente da linguaggi specifici e solo in un secondo tempo collocare nello schema i "linguaggi veri". Questo è l'unico approccio che permette di mettere in evidenza le similitudini tra linguaggi (ed anche tra paradigmi) apparentemente molto diversi. Allo stesso tempo si rende così più facile il compito di imparare linguaggi diversi. Nella mia esperienza di docente, anche dopo molti anni gli ex-studenti si ricordano i principi appresi nel corso e continuano ad apprezzarne l'approccio, che ha loro permesso di adattarsi alle evoluzioni delle tecnologie senza grandi difficoltà. Il testo di Gabbrielli e Martini ha come riferimento prevalente un corso della laurea triennale in Informatica. Per questa ragione non ha prerequisiti complessi e affronta l'argomento trovando un perfetto equilibrio tra rigore e semplicità. Particolarmente apprezzabile e riuscito è lo sforzo di mettere in luce il collegamento con alcuni "pezzi di teoria" importanti (come i linguaggi formali, la calcolabilità, la semantica), che il libro giustamente richiama, anche perché in molti corsi di laurea triennali questi argomenti non vengono più trattati. Prima di concludere, mi permetto un accenno polemico al fatto che purtroppo in alcuni corsi di laurea triennali (tra questi, quello in cui io insegno) sono stati eliminati dai corsi fondamentali oltre ai tradizionali contenuti di informatica teorica anche i contenuti di linguaggi, considerati anch'essi troppo teorici per un corso professionalizzante orientato alle nuove tecnologie. Gli studenti conoscono (o credono di conoscere) alla perfezione un unico linguaggio, magari Java, con tutte le cose "che servono", incluse le librerie. Ma non sapranno mai descrivere il linguaggio che usano in termini di concetti generali, perché nulla sanno di metodi per il passaggio di parametri, di regole di scoping e di polimorfismo. Almeno ora avranno la possibilità di imparare queste cose su un ottimo testo scritto in italiano. Giorgio Levi | << | < | > | >> |Pagina xvIntroduzioneFacilius per partes in cognitionem totius adducimur. (Seneca, Epist., 89, 1) L'apprendimento di un linguaggio di programmazione costituisce per molti studenti il rito d'iniziazione all'informatica. Si tratta di un passaggio importante, ma che presto si rivela insufficiente: tra gli attrezzi del mestiere ci sono molti linguaggi ed una competenza importante del bravo informatico è quella di saper passare da un linguaggio all'altro (e di apprenderne di nuovi) con naturalezza e velocità. Questa competenza non la si acquisisce soltanto imparando ex novo molti linguaggi diversi. Come le lingue naturali, anche i linguaggi di programmazione hanno tra loro somiglianze, analogie, fenomeni di importazione dall'uno all'altro, genealogie che ne influenzano le caratteristiche. Se è impossibile imparare bene decine di linguaggi di programmazione, è possibile conoscere a fondo i meccanismi che ispirano e guidano il progetto e l'implementazione di centinaia di linguaggi diversi. Questa conoscenza delle "parti" facilita la comprensione del "tutto" costituito da un nuovo linguaggio, fornendo dunque una competenza metodologica fondamentale nella vita professionale dell'informatico, in quanto consente di anticipare l'innovazione e di sopravvivere all'obsolescenza delle tecnologie. È per questi motivi che un corso sugli aspetti generali dei linguaggi di programmazione è, in tutto il mondo, un passaggio chiave della formazione avanzata (universitaria o professionale) di un informatico. Relativamente ai linguaggi di programmazione, le competenze fondamentali che un informatico deve possedere sono almeno di quattro tipi: • gli aspetti propriamente linguistici; • come i costrutti linguistici possono essere implementati ed il relativo costo; • gli aspetti architetturali che influenzano l'implementazione; • le tecniche di traduzione (compilazione). È raro che un singolo corso possa affrontare tutti e quattro questi aspetti. In particolare, la descrizione degli aspetti architetturali e delle tecniche di compilazione costituiscono ciascuno un argomento sufficientemente complesso ed elaborato da meritare una trattazione separata. Gli altri due aspetti sono il contenuto primario di un corso generale sui linguaggi di programmazione e costituiscono il contenuto principale di questo testo. La letteratura anglosassone, a differenza di quella in lingua italiana, è ricca di testi che affrontano questi argomenti, alcuni carichi di storia e sui quali si sono formate generazioni di studenti. Tutti questi testi, tuttavia, hanno in mente un lettore avanzato che conosca già diversi linguaggi di programmazione, che abbia una competenza non superficiale dei meccanismi di base fondamentali, che non si spaventi davanti a frammenti di codice espressi in linguaggi a lui ignoti (perché riesce a comprenderli per analogia o per differenza da quelli che già conosce). Si tratta di testi, dunque, che potremmo chiamare di "linguaggi comparati": estesi, approfonditi, stimolanti. Ma troppo estesi e approfonditi (in una parola: difficili) per lo studente che inizia il suo cammino con un solo (o al massimo due) linguaggi di programmazione e che deve ancora approfondire i concetti di base. Questo testo intende colmare questa lacuna. Gli esperti vedranno che l'indice delle materie ripercorre in larga misura i temi classici. Ma questi stessi temi sono trattati in modo elementare, cercando di assumere come prerequisiti solo il minimo indispensabile e sforzandosi di non fare un catalogo delle opzioni possibili nei diversi linguaggi di programmazione esistenti. Il lettore di riferimento è quello che conosce (bene) un linguaggio (per esempio Pascal, C, C++, o Java); meglio se ha avuto anche una qualche esposizione ad un altro linguaggio o ad un altro paradigma. Si sono evitati riferimenti consistenti a linguaggi ormai desueti e gli esempi di codice sono solo raramente espressi in uno specifico linguaggio di programmazione: il testo usa con libertà una sorta di pseudolinguaggio (ispirato nella sua sintassi concreta a C e Java), cercando di descrivere così gli aspetti più rilevanti che uniscono i diversi linguaggi. Di tanto in tanto un "riquadro" a capo pagina presenta un approfondimento, o il richiamo di una nozione di base, o qualche dettaglio specifico dei linguaggi più comuni (C, C++, Java 5.0; ML e LIsP per i linguaggi funzionali; PROLOG per i linguaggi logici). Il materiale dei riquadri può quasi sempre essere tranquillamente omesso in una prima lettura. Tutti i capitoli presentano una breve serie di esercizi, intesi come banco di prova per la comprensione del materiale. Non vi sono esercizi davvero difficili o che richiedano più di una decina di minuti per esser risolti. Il Capitolo 3 (Fondamenti) affronta temi che solitamente non sono trattati in un testo di linguaggi di programmazione. È tuttavia naturale, discutendo di semantica statica e confrontando tra loro linguaggi, chiedersi quali siano i limiti dell'analisi statica dei programmi e se quello che si può fare in un linguaggio si possa fare anche in un altro. Invece che rimandare ad altri testi, vista la rilevanza sia culturale che pragmatica di queste questioni, abbiamo deciso di rispondere direttamente a queste domande. In modo informale, ma rigoroso, nel contesto di poche pagine viene presentata l'indecidibilità del problema della fermata e l'equivalenza dei linguaggi di programmazione quanto alle funzioni calcolabili. Questo permette di esporre lo studente, che non sempre ha nel suo curriculum un corso completo sui "fondamenti", ai risultati principali relativi alle limitazioni dei procedimenti di calcolo, che riteniamo fondamentali per la sua formazione. Insieme ai principi, il testo introduce anche ai tre principali paradigmi di programmazione: quello orientato agli oggetti (un tema ormai obbligato della formazione dell'informatico), quello funzionale, quello logico. La necessità di realizzare un testo introduttivo e, insieme, di mantenerne l'estensione in un numero di pagine ragionevole, spiega l'esclusione di temi importanti, quali la concorrenza e i linguaggi di scripting, che costituiscono competenze importanti ma che difficilmente possono essere affrontati correttamente assieme agli aspetti di base. | << | < | > | >> |Pagina 28710
Il paradigma orientato agli oggetti
Presenteremo in questo capitolo gli aspetti salienti dei linguaggi orientati agli oggetti, un paradigma che vede i suoi precursori in Simula (fine anni '60) e Smalltalk (anni '70) e che ha ottenuto enorme successo, anche commerciale, nei due decenni seguenti (C++ e Java sono solo i due linguaggi più noti tra i tanti che sono tutt'oggi in uso). "Orientato agli oggetti" è ormai una locuzione abusata, che troviamo applicata non solo ai linguaggi di programmazione, ma anche ai metodi di sviluppo del software, alle basi di dati, ai sistemi operativi ecc. Il nostro tentativo sarà quello di presentare gli aspetti linguistici che riguardano gli oggetti e il loro uso, facendo qualche sporadico accenno alle tecniche di progetto orientate agli oggetti, per le quali, tuttavia, non possiamo che rimandare alla (sterminata) bibliografia. Anche dopo aver così ristretto il campo, ci sono molti modi di avvicinarsi ai concetti che ci interessano. Noi seguiremo quello che crediamo il più semplice: presenteremo gli oggetti come un modo per ottenere astrazione sui dati, in modo flessibile ed estendibile. Inizieremo perciò il nostro studio mostrando alcune limitazioni delle tecniche che abbiamo introdotto nel Capitolo 9: questi limiti ci suggeriranno alcuni concetti che, di fatto, costituiscono i cardini di un linguaggio orientato agli oggetti. Dopo aver approfondito questi aspetti caratteristici, studieremo alcune estensioni delle soluzioni linguistiche oggi disponibili nei linguaggi commerciali, con riferimento in particolare alla nozione di sottotipo, polimorfismo e genericità. Secondo lo stile che abbiamo mantenuto in tutto il testo, cercheremo di rimanere indipendenti da uno specifico linguaggio di programmazione, anche se questo non sarà sempre possibile. Il linguaggio al quale maggiormente ci ispireremo sarà Java, per la coerenza del suo progetto, che ci permette di discutere dei concetti (e non dei dettagli linguistici) in modo più chiaro e sintetico di altri linguaggi. | << | < | > | >> |Pagina 34311
Il paradigma funzionale
Presentiamo in questo capitolo gli aspetti principali del paradigma di programmazione funzionale, nel quale la computazione avviene per riscrittura di funzioni e non per modifica dello stato. La caratteristica fondamentale dei linguaggi di questo paradigma, almeno nella loro versione "pura", è proprio quella di non possedere il concetto di memoria (e, dunque, di effetto collaterale): fissato un ambiente, un'espressione denota sempre lo stesso valore. Discuteremo il paradigma puro nelle prime sezioni, evidenziandone gli aspetti fondamentali. I linguaggi di programmazione funzionale, tuttavia, immergono questi ingredienti "puri" in un contesto che aggiunge molti altri meccanismi, che passeremo in rassegna nel Paragrafo 11.3. Accenneremo quindi alla macchina SECD, una macchina astratta per linguaggi funzionali di ordine superiore che costituisce il prototipo di molte implementazioni reali. Saremo a questo punto nella posizione di discutere i motivi che rendono interessante il paradigma di programmazione funzionale, in rapporto agli ordinari linguaggi imperativi. Conclude il capitolo un paragrafo più teorica che fornisce una succinta introduzione al lambda-calcolo, un sistema formale per la calcolabilità al quale tutti i linguaggi funzionali si ispirano e che ha costituito, sin dai tempi di ALGOL e LISP, un modello costante per il progetto dei linguaggi di programmazione. | << | < | > | >> |Pagina 37712
Il paradigma logico
In questo capitolo analizziamo l'altro paradigma che, insieme a quello funzionale, permette la programmazione dichiarativa: il paradigma logico che include sia linguaggi teorici sia linguaggi effettivamente implementati ed usati, dei quali il più noto è sicuramente PROLOG. Anche se vi sono differenze abbastanza rilevanti di ordine pragmatico e, per certi versi, teorico, fra i vari linguaggi logici, questi condividono l'idea di intendere la computazione come deduzione logica.
In questo capitolo vedremo dunque queste nozioni, cercando di limitare la
parte teorica, adottando anche per questo paradigma l'approccio che ha
caratterizzato tutto il testo. Non intendiamo quindi insegnare a programmare in
PROLOG, anche se faremo vedere vari esempi di programmi reali, ma intendiamo
fornire la basi per poter comprendere e, in breve tempo, padroneggiare, questo
ed altri linguaggi logici.
12.1 Deduzione come computazione Un noto slogan, dovuto a R. Kowalski, così sintetizza la nozione alla base dell'attività di programmazione: Algoritmo = Logica + Controllo. Secondo questa "equazione" la specifica di un algoritmo, e quindi la sua formulazione in termini di un linguaggio di programmazione, può essere separata in due parti. Da un lato si specifica la logica della soluzione, ossia si definisce "cosa" debba essere fatto. Dall'altro invece si specificano gli aspetti relativi al controllo, e quindi si chiarisce "come" si deve arrivare alla soluzione desiderata. Il programmatore che usi un tradizionale linguaggio imperativo deve tener contro di entrambe queste componenti, usando i meccanismi che abbiamo diffusamente analizzato nei capitoli precedenti. La programmazione logica, invece, fu definita originariamente con l'idea di separare nettamente questi due aspetti. Al programmatore è richiesta solo, almeno in linea di principio, la specifica della parte logica. Tutto quanto riguarda il controllo è demandato alla macchina astratta: usando un meccanismo computazionale basato su una particolare regola di deduzione (la risoluzione), essa ricerca nello spazio delle possibili soluzioni quella specificata dalla "logica", definendo in questo modo la sequenza di operazioni necessarie ad arrivare al risultato finale. Le basi di questa visione della computazione come deduzione logica possono essere ricondotte ai lavori di K. Gödel e J. Herbrand negli anni '30. In particolare, Herbrand anticipò, anche se in modo non completamente formale, alcune idee relative al processo di unificazione che, come vedremo, costituisce il meccanismo computazionale di base dei linguaggi logici. Bisognerà attendere fino agli '60 per una definizione formale di tale processo (ad opera di A. Robinson) e solo dieci anni ancora più tardi (nei primi anni '70) ci rese conto che la dimostrazione automatica di formule di un tipo particolare poteva essere interpretata come un meccanismo computazionale. Nascevano così i primi linguaggi di programmazione del paradigma logico, tra cui PROLOG (il nome è un acronimo per PROgramming in LOGic, per ulteriori informazioni sulla storia del linguaggio si veda il Paragrafo 13.4).
Oggi ci sono molte versioni implementate di PROLOG ed esistono vari altri
linguaggi logici reali (di particolare interesse, per le applicazioni, sono
quelli con vincoli). Tutti questi linguaggi, PROLOG per primo, per motivi di
efficienza permettono l'uso di costrutti che riguardano anche aspetti di
specifica del controllo. Questi costrutti, non avendo una diretta
interpretazione logica, rendono molto più complicata la semantica del programma
e fanno perdere parte della natura puramente dichiarativa del paradigma logico.
Nonostante ciò, anche in presenza di questi aspetti "impuri", si tratta pur
sempre di linguaggi di programmazione che richiedono al programmatore poco più
che formulare (o dichiarare) la specifica del problema da risolvere. In alcuni
casi i programmi risultanti sono davvero sorprendenti per semplicità, brevità e
chiarezza, come vedremo nel prossimo paragrafo.
|