Prometheo - Home Page Articolo - Valutazione dei Linguaggi
Anche se non ha senso dire che, in generale, un linguaggio è migliore di un altro, né tanto meno redigere una classifica dei linguaggi di programmazione, è possibile però individuare ed analizzare le caratteristiche che entrano in gioco nella scelta del linguaggio da utilizzare in una determinata situazione. Ed è ciò che tentiamo di fare in questo articolo
 
Home Page | Chi Siamo | Le Sedi | Come Contattarci | Franchising | Patente Europea


Precedente
Su
Successiva
Corsi: Informatica; Nuove Professioni; Professionali; Crescita; Calendario | Sommario
 

Articolo
Valutazione dei Linguaggi di Programmazione

di Massimo Di Bello

Anche se non ha senso dire che, in generale, un linguaggio è migliore di un altro, né tanto meno redigere una classifica dei linguaggi di programmazione, è possibile però individuare ed analizzare le caratteristiche che entrano in gioco nella scelta del linguaggio da utilizzare in una determinata situazione. Ed è ciò che tenteremo di fare nel seguito.
Prima di tutto possiamo suddividere tali caratteristiche in due categorie: i fattori intrinseci ed i fattori ambientali. I fattori intrinseci sono strettamente legati al linguaggio, alla sua struttura, alla sua filosofia, e possono variare solo se il linguaggio stesso cambia (con nuove versioni). Quelli ambientali invece fanno riferimento a come il linguaggio è entrato nel mondo reale, per cui sono legati anche ad altre variabili non direttamente dipendenti dal linguaggio (potenza degli elaboratori disponibili, presenza di librerie, diffusione raggiunta, ecc.) e possono quindi anche variare con il passare del tempo anche se il linguaggio non viene modificato.
E' da sottolineare che molte di queste caratteristiche, per la loro stessa natura, sono difficili da definire, non sono precisamente quantificabili, spesso sono interdipendenti e la loro valutazione ha una forte componente soggettiva.

Fattori intrinseci

Nella categoria dei fattori intrinseci abbiamo individuato i seguenti fattori: espressività, facilità di apprendimento, qualità pedagogiche, leggibilità, robustezza, facilità di manutenzione, modularità, parametricità, plasmabilità, generalità, grado di astrazione, efficienza, tipo di traduttore disponibile, complessità del traduttore e, infine, gestione Input/Output.
Espressività: potremo definirla come la facilità con cui, utilizzando un determinato linguaggio, è possibile esprimere la soluzione ad un dato problema. Tale fattore, quindi, determina la velocità con cui possono essere risolti i problemi mediante l'uso di un linguaggio, la quantità di codice necessaria e persino la qualità del risultato finale. Infatti il linguaggio utilizzato ha una notevole influenza anche sul modo in cui si risolve un problema. E' senz'altro applicabile, anche ai linguaggi di programmazione, quanto ha osservato il linguista Benjamin Lee Whorf sui linguaggi naturali: "Il linguaggio dà forma al modo di pensare e determina ciò di cui si può pensare".
La valutazione dell'espressività di un linguaggio dipende, ovviamente, dal tipo di problema in esame e da quanto il linguaggio sia congeniale al suo utilizzatore tipo.
Ad esempio scrivere in Lisp un programma che calcoli la derivata simbolica di una espressione matematica, richiede solo 20/30 righe di codice. Usare il C comporterebbe una quantità di codice notevolmente maggiore, con un maggiore sforzo da parte del programmatore, ottenendo comunque un risultato, probabilmente più efficiente, ma di qualità molto minore (in termini soprattutto di leggibilità e modificabilità del codice). Ciò perché il Lisp, per sua natura, è un linguaggio particolarmente orientato alla manipolazione simbolica.
Facilità di apprendimento: nella valutazione di tale fattore vanno principalmente considerati la complessità del linguaggio, il tipo di background culturale necessario al suo apprendimento e anche un fattore esterno: la presenza di buoni manuali.
Il Basic (Beginners All-purpose Symbolic Instruction Code) deve la sua diffusione proprio alla sua estrema semplicità, in quanto ha poche regole grammaticali e può essere imparato in poco tempo (notare che qui e di seguito, se non altrimenti specificato quando parliamo di Basic, intendiamo fare riferimento al linguaggio originale, non alle sue evoluzioni quali il Visual Basic).
Dall'altra parte il C, malgrado il suo numero ridotto di istruzioni e di regole, risulta essere di non facile apprendimento a causa della sua notazione, a volte oscura, e soprattutto per le sue caratteristiche a basso livello e, in particolare per l'ampia possibilità di utilizzo dei puntatori. In realtà i progettisti del C, nella definizione del linguaggio, hanno sempre puntato verso l'efficienza dell'eseguibile e la semplicità del compilatore, sacrificando a volte la chiarezza del linguaggio.
Infine, come ultimo esempio, consideriamo il Prolog il cui apprendimento presenta delle difficoltà sia perché è necessario prima acquisire alcune conoscenze di logica sia perché richiede, per le sue caratteristiche di linguaggio dichiarativo, un modo di affrontare i problemi molto diverso da quello usuale.
Qualità pedagogiche: indica l'attitudine del linguaggio ad essere utilizzato come strumento per l'insegnamento della programmazione.
Il Pascal, nato proprio come veicolo didattico, è in generale considerato il più adatto a tale scopo, molto più del Basic che, malgrado la sua semplicità, presenta troppe lacune (ad esempio non è strutturato e manca di strutture dati astratte) che ne diminuiscono le valenze formative. Oggi un buon candidato può essere considerato anche il Visual Basic che ha tratto molto proprio dal Pascal, integrandolo alla facilità del Basic. Un altro linguaggio con una forte vocazione didattica è il Logo, usato soprattutto per insegnare nozioni di programmazione ai bambini. Tale sua peculiarità ha favorito la creazione di versioni nazionalizzate, per cui le parole chiavi del linguaggio sono tratte dall'italiano (e non dal solito inglese). La nazionalizzazione di un linguaggio ha, ovviamente, senso solo da un punto di vista didattico, in quanto comporta anche svantaggi non trascurabili, primo fra tutti una minore portabilità del codice e una minore comunicabilità tra programmatori di differenti lingue.
Notiamo esplicitamente che un linguaggio può avere caratteristiche formative pur non essendo di facile apprendimento o non adatto a neofiti. Come caso limite, possiamo considerare lo stesso Assembler il cui insegnamento risulta molto formativo, in quanto mostra le operazioni base eseguibili da un microprocessore.
Interessanti, da questo punto di vista, sono da considerarsi anche il Lisp e il Prolog sia per la loro originalità sia perché imparandoli si acquisiscono tecniche di risoluzione di problemi efficacemente utilizzabili, in determinate situazioni, anche negli altri linguaggi di programmazione più "normali".
Leggibilità: è in relazione con la facilità con cui il progettista e/o persone estranee ad un progetto possono leggere e comprendere il relativo programma. Ovviamente questa qualità, riferita ad un determinato programma, dipende principalmente dallo stile di programmazione usato e dal rispetto delle regole di buona educazione (uso di nomi significativi, utilizzo appropriato delle indentazione, rispetto delle convenzioni universalmente accettate, ecc.); ciononostante essa può essere facilitata o meno dal linguaggio di programmazione. Ad esempio solo i linguaggi più moderni concedono piena libertà nel posizionare le istruzioni, permettendo così l'indentazione.
Uno dei linguaggi meno leggibili è senz'altro l'APL (A Programming Language). Nato per risolvere problemi scientifici e matematici, con forte presenza di dati in vettori e matrici, fa largo uso (oltre al normale insieme di caratteri ascii) di simboli speciali, che lo rendono, ai non iniziati, del tutto illeggibile.
E' necessario, comunque, distinguere tra leggibilità per neofiti e per esperti. Ad esempio un, a prima vista illeggibile ed ostico,
i++;
utilizzato in C per incrementare una variabile, per un programmatore esperto è molto più leggibile di un ridondante
i:=i+1;
Ancora, se è vero che la prolissità del Cobol lo rende leggibile anche da parte di non programmatori, per un matematico (ma non solo) dover usare espressioni del tipo:
MULTIPLY BASE BY ALTEZZA GIVING AREA
è non solo poco naturale, ma nel caso di espressioni più complesse, praticamente illeggibile. Molto meglio il più conciso e soprattutto simile alla notazione algebrica:
AREA=BASE*ALTEZZA
che, per fortuna, si utilizza nella maggior parte degli altri linguaggi.
La leggibilità di un linguaggio non è assoluta ma dipende anche dal tipo di problema affrontato e dalla lunghezza del codice utilizzato per la sua soluzione. Ad esempio i programmi scritti in Basic sono di immediata comprensione fin tanto che restano di dimensioni limitate. Quando comincia a crescere la complessità del programma, la mancanza di costrutti strutturati rende sempre più difficoltosa la lettura del sorgente.
Ancora, la risoluzione di alcune tipologie di problemi con il Prolog, sono costituiti da programmi di una leggibilità esemplare. In molti altri casi è necessario rincorrere a contorsioni algoritmiche che portano, invece, ad un codice completamente ermetico.
Robustezza: è la capacità del linguaggio (a volte a costo di una minore flessibilità e potenza) di impedire al programmatore, per quanto possibile, di inserire dei bug all'interno del codice. Ovviamente anche qui, come nel caso della leggibilità, molto dipende dal programmatore, dal suo stile, dall'uso di tecniche di programmazione difensiva.
Il linguaggio meno robusto è senz'altro l'Assembler, in quanto dà libertà illimitata al programmatore, permettendogli addirittura di creare codice automodificante. I linguaggi ad alto livello, invece, hanno via via introdotto una serie di caratteristiche atte proprio a diminuire il rischio di incorrere nei bug. Ad esempio la programmazione strutturata, limitando i tipi di controllo di flusso (in pratica vietando i GOTO indiscriminati) e favorendo una modularizzazione del codice va proprio in questa direzione. Così anche il strong type checking (controllo rigoroso dei tipi), oltre a permettere la creazione di eseguibili più efficienti, costituisce un modo efficace per evitare insidiosi bug.
Fra i linguaggi robusti si distingue il Pascal che è pervaso da un forte "senso materno", al contrario del C che segue, invece, la filosofia secondo cui se un programmatore fa una determinata azione, anche se potenzialmente pericolosa, si deve presupporre che sa cosa sta facendo, per cui bisogna lasciargliela fare.
Facilità di manutenzione: intesa come facilità con cui è possibile modificare un programma per venire incontro a nuove esigenze o per eliminare problemi emersi nel suo uso. Il fatto è che tutti i programmi, che riescono a suscitare un minimo di interesse, sono soggetti ad una continua evoluzione. Si stima che gran parte del tempo di sviluppo è impiegato non nella creazione di nuovi programmi ma nella modifica di programmi esistenti. Da qui l'importanza notevole di tale fattore, che risulta essere fortemente legato alla modularità del linguaggio. Questo fattore è anche il motivo del considerevole successo avuto dai linguaggi ad oggetti, che pur rallentando le normali fasi di sviluppo (richiedendo un maggior sforzo di analisi iniziale per individuare e organizzare le gerarchie di classi opportune) facilitano drasticamente la manutenzione nel tempo del software.
Modularità: è la capacità del linguaggio di favorire suddivisione di un programma in parti di codice quanto più indipendenti l'uno d'altro. Ciò facilità la creazione e la manutenzione del programma (principio del divide et impera) ed, inoltre, aumenta la possibilità di riutilizzare lo stesso codice per altre applicazioni. Solitamente ciò si raggiunge con la possibilità di avere sottoprogrammi (procedure, funzioni, subroutine, ecc. a secondo della terminologia del linguaggio) e con tecniche di information hiding per avere delle scatole nere di cui all'esterno si può accedere solo attraverso un ben determinato protocollo.
Parametricità: cioè la capacità di poter creare programmi che consentono la risoluzione di più problemi contemporaneamente.
Il Prolog, ad esempio, in alcuni casi (purtroppo solo in problemi relativamente semplici) risulta essere estremamente potente, proprio grazie alle sue caratteristiche di linguaggio dichiarativo. Esemplare è il codice che permette di calcolare la lista unione di due liste. Esaminiamone le due regole che definiscono il predicato append (in corsivo ne riportiamo il relativo significato per maggiore chiarezza):
append([], L, L).
se la prima lista è vuota allora l'unione (la terza lista) è uguale alla seconda
append([T|C], L, [T|R]):- append(C, L, R).
la lista unione è uguale al primo elemento della prima lista seguita dalla lista che è l'unione della restante parte della prima lista e della seconda
Questo codice è molto simile a ciò che si potrebbe scrivere in Lisp dato che entrambi supportano le liste come tipo di dato built-in (ovvero predefinito), e supportano facilmente le definizioni ricorsive. Infatti in Lisp possiamo definire la funzione append in questo modo:
(defun append (l m)
  (if (null l)
      m
      (cons (car l) (append (cdr l) m))))

Però vi è una importante differenza: mentre il codice Lisp permette solo di calcolare l'unione di due liste, il codice Prolog riportato permette anche di effettuare altre operazioni correlate: data una lista scomporla in tutti i modi possibili in due segmenti consecutivi separati di cui essa è l'unione, oppure data una lista e una sua parte iniziale di ottenerne la parte rimanente, e così via. Ciò in quanto il codice Lisp specifica come calcolare appunto l'unione di due liste (approccio procedurale), mentre il codice Prolog specifica solo cosa si intende dire quando si dice che una lista è l'append di due liste (approccio dichiarativo).
Plasmabilità: la possibilità di poter modificare o estendere la sintassi del linguaggio. I linguaggi tradizionali (Pascal, Cobol, C, ecc.) presentano poca flessibilità da questo punto di vista. Invece il Prolog permette di definire nuovi operatori (con priorità e tipo di associazione desiderato), e anche il Lisp data la intrinseca uguaglianza tra programmi e dati (le funzioni non sono altro che liste) presenta un buon grado di plasmabilità. I linguaggi ad oggetti presentano in genere un notevole grado di plasmabilità. Ad esempio il C++ permette l'overloading degli operatori, per cui si possono ridefinire gli usuali operatori (+, -, *, ecc.) a secondo del tipo di dati su cui operano. Per cui, ad esempio, il C++ non supporta direttamente come strutture dati le liste, ma data la sua versatilità non solo si possono definire come tipo utente ma, ridefinendo opportunamente i vari operatori necessari, renderle praticamente indistinguibile dai tipi base.
Generalità: il grado con cui il linguaggio può essere utilizzato in diversi campi di applicazione. Ci sono molti linguaggi che anche se non hanno raggiunto una larga diffusione hanno però trovato una propria nicchia ad esempio il Forth diffuso nell'ambito del controllo di processo.
Così come il Cobol che difficilmente viene utilizzato al di fuori dall'area dei gestionali.
Ben definizione: la sintassi e la semantica del linguaggio devono essere privi di ambiguità e avere una coerenza interna.
Grado di astrazione: si riferisce al livello di astrazione sui dati e sul controllo. Alcuni linguaggi permettono più facilmente di effettuare la Metaprogrammazione: ovvero di creare programmi che controllano lo stato della propria esecuzione, agendo di conseguenza.
Efficienza: è la capacità del linguaggio di creare applicazioni efficienti sia in termini di memoria che di velocità. I linguaggi più astratti tendono a produrre codice più inefficienti sia perché lo sono strutturalmente sia perché, proprio per la loro astrazione, risulta più semplice scrivere programmi inefficienti con essi.
Ad esempio in Lisp viene naturale calcolare i valori della successione di Fibonacci (in cui ogni elemento è la somma dei due valori precedenti: 1, 1, 2, 3, 5, 8, 13, ...) ricalcandone fedelmente la definizione ricorsiva:
fib(1) = 1; fib(2) = 1; fib(n) = fib(n-1) + fib(n-2)
ottenendo il seguente codice:
(defun fib(n)
    (if (or (= n 1) (= n 2))
         1
         (+ (fib (- n 1)) (fib (- n 2)))))

che va calcolare più e più volte gli stessi valori in una maniera terribilmente inefficiente!
Tipo di traduttore disponibile: esistono due tipi di traduttori, interpreti e compilatori. Il primo partendo dal file sorgente traduce un istruzione alla volta, la manda in esecuzione, e quindi passa alla istruzione seguente. Un compilatore, invece, traduce l'intero programma in un colpo solo, ottenendo un file direttamente eseguibile. La compilazione risulta in una esecuzione molto più veloce, mentre l'interpretazione permette di sviluppare un programma in maniera più interattiva.
Anche se in linea di principio tutti i linguaggi potrebbero essere tradotti in entrambi modi spesso è disponibile solo un tipo di traduttore, o comunque il linguaggio non risulta essere, per le sue caratteristiche, molto adatto ad un tipo di traduzione. Ed è questo il motivo per cui lo consideriamo un fattore di tipo intrinseco.
Ad esempio il C è un linguaggio tipicamente compilato, mentre il Basic è interpretato (anche se per entrambi è disponibile l'altro tipo di traduttore).
Complessità del traduttore: la struttura del linguaggio può essere tale da richiedere, per l'operazione di traduzione, notevoli risorse di memoria e tempi rilevanti. Inoltre bisogna considerare anche la difficoltà di implementazione del traduttore. La complessità del traduttore è influenzata non solo dalla struttura di base del linguaggio ma anche da alcune scelte, marginali ai fini della personalità del linguaggio, fatte dai progettisti. Ad esempio in PL/I le parole chiavi (ovvero le parole che fanno parte integranti del linguaggio) non sono riservate (anche perché sono numerose), per cui anche parole chiavi come IF e THEN si possono utilizzare come identificatori ed è compito del compilatore distinguere i due tipi di uso a secondo del contesto (complicando così di molto la fase di analisi lessicale, cioè la fase di identificazione dei simboli presenti nel sorgente).
La complessità del traduttore ha un notevole impatto sulla diffusione del linguaggio.
Gestione Input/Output: la presenza di strumenti atti a facilitare l'accesso sequenziale e casuale ai file e il supporto all'utilizzo di database. Alcuni linguaggi danno un supporto diretto ed esteso all'I/O (primo fra tutti il Cobol), altri hanno bisogno di librerie esterne, altri ne sono del tutto privi.

Fattori ambientali

I fattori ambientali sono: diffusione, integrazione, ambienti di sviluppo disponibili, portabilità e grado di standardizzazione
Diffusione: quanto più l'utilizzo di un linguaggio è diffuso, maggiore è la disponibilità di documentazione, librerie, strumenti di sviluppo, piattaforme su cui è implementato, e infine c'è un maggiore sforzo da parte dei produttori (anche per la maggiore concorrenza che si viene a creare) per sviluppare compilatori efficienti e che producono codice efficiente.
Integrazione: come abbiamo detto a secondo del tipo di applicazione un linguaggio può essere più o meno adatto; per grossi progetti può essere quindi utile sviluppare i vari moduli in linguaggi diversi, in modo da usufruire delle migliori caratteristiche di ciascuno. Per integrazione, quindi, intendiamo la possibilità di chiamare codice scritto in altri linguaggi e viceversa.
Ambienti di sviluppo disponibili: malgrado gli sforzi attuali per sviluppare una metodologia sistematica (ingegneria del software), la programmazione è ancora in gran parte un'arte, per cui rivestono notevole importanza gli strumenti di supporto a tale attività: editor integrato, debugger simbolici, profiler, help on line, ecc. Tale fattore è in generale influenzato dalla diffusione, con alcune pregevoli eccezioni (Lisp).
Portabilità: è strettamente legato alle piattaforme hardware/software su cui è implementato. Spesso è determinante la presenza di una standardizzazione ufficiale (ma non essenziale ad esempio nel caso del C, il libro di Kernighan e Ritche ha determinato uno standard de facto).
Grado di standardizzazione: anche i linguaggi di programmazione, come quelli naturali, soffrono della presenza dei dialetti. Per cui di uno stesso linguaggio ne possono esistere più versioni tra loro incompatibili anche per piccoli particolari. Ciò, come per i dialetti naturali, porta a problemi di incomunicabilità: per poter utilizzare un programma scritto in un altro dialetto, è necessario apportarvi delle modifiche (a volte lievi, a volte sostanziali).
Il linguaggio che soffre di più di tale situazione è il Basic. Anche il Lisp in gioventù, ne ha sofferto, poi è stato creato un apposito comitato per la standardizzazione che ha prodotto il Common Lisp, che ora ha quasi del tutto soppiantato i precedenti.
Di norma è accettabile un certo grado di tolleranza verso estensioni del linguaggio resi disponibili dalle varie implementazioni, purché queste non influenzino il nucleo originale e siano ben distinte da queste (magari disattivabili mediante opzioni del compilatore). Anzi tale usanza risulta essere determinante per l'evoluzione del linguaggio stesso, rappresentando un palestra dove sperimentare nuovi costrutti, e spesso quelli che hanno riscosso un maggior successo vengono inserite nelle versioni standard successive del linguaggio.

Conclusioni

Quale linguaggio bisogna imparare allora? Per chi inizia è senz'altro da consigliare il Visual Basic. In ogni caso è importante tenere presente quando si impara un nuovo linguaggio, che bisogna capirne la filosofia di base, l'idea o le idee che stanno sotto. Molti programmatori lavorano in C, come se stessero usando il Pascal (addirittura alcuni utilizzano dei zuccheri sintattici definendo delle macro del tipo begin e end uguali alle parentesi graffe, facendo scarso uso dei puntatori e così via). Ancora, programmare in Lisp vuol dire entrare in un modo diverso di vedere le cose: in Lisp è naturale costruire tante piccole funzioni che si richiamano tra di loro, anche se il Lisp permette un tipo di programmazione imperativo che risulta però non naturale da usare.
In generale è preferibile conoscere più di un linguaggio, sia per essere in grado di poter scegliere il più adatto in base al tipo di applicazione da sviluppare sia perché, anche se poi ne andiamo ad usare uno solo, avere una conoscenza più ampia ci permette di comprenderlo meglio e quindi in definitiva di saperlo utilizzare meglio.
In conclusione possiamo dire che c'è nella comunità internazionale uno sforzo continuo sia nel migliorare linguaggi già esistenti sia di crearne di nuovi. L'obiettivo di fondo è creare linguaggi che permettano di sviluppare in minor tempo, programmi migliori.

© Massimo Di Bello  <Prometheo Staff>
mdibello@prometheo.it
Articolo#05: [pubblicato il: 28/06/99]

Pagine Utili

Indice Articoli
Articolo
I Linguaggi di Programmazione

 

 

Vuoi ricevere gratuitamente utili informazioni e curiosità direttamente via email?

Prometheo Srl - Gli articoli sono Copyright © 1999 - 2003 dei rispettivi Autori, pubblicati sul sito web Prometheo per gentile concessione degli Autori

Copyright © 1999 - 2017 Prometheo Srl - Per informazioni: info@prometheo.it (Informativa Privacy) - Numero Verde: 800.41.68.98

I prodotti citati sono marchi e marchi registrati dei relativi produttori. Ultimo Aggiornamento: 23/06/14.

Precedente ] Home ] Su ] Successiva ]