Loading AI tools
linguaggio formale usato per dare istruzioni a un calcolatore Da Wikipedia, l'enciclopedia libera
Un linguaggio di programmazione è un sistema di notazione per la scrittura di programmi per computer. La maggior parte dei linguaggi di programmazione sono linguaggi formali basati su testo, ma possono anche essere grafici. Sono una sorta di linguaggio informatico.
La descrizione di un linguaggio di programmazione è solitamente divisa nelle due componenti della sintassi (forma) e della semantica (significato), che di solito sono definite da un linguaggio formale. Alcuni linguaggi sono definiti da un documento di specifica (ad esempio, il linguaggio di programmazione C è specificato da uno standard ISO) mentre altri linguaggi (come Perl) hanno un'implementazione dominante che viene trattata come riferimento.
Alcuni linguaggi hanno entrambi, con il linguaggio di base definito da uno standard e le estensioni prese dall'implementazione dominante che sono comuni. La teoria dei linguaggi di programmazione è il sottocampo dell'informatica che studia la progettazione, l'implementazione, l'analisi, la caratterizzazione e la classificazione dei linguaggi di programmazione.
Il primo linguaggio di programmazione della storia è il linguaggio meccanico adoperato da Ada Lovelace per la programmazione della macchina di Charles Babbage, al quale fu seguito il Plankalkül di Konrad Zuse, sviluppato da lui nella Svizzera neutrale durante la seconda guerra mondiale e pubblicato nel 1946. Plankalkül non venne mai realmente usato per programmare. La programmazione dei primi elaboratori veniva fatta invece in short code[1], da cui poi si è evoluto l'assembly, che costituisce una rappresentazione simbolica del linguaggio macchina. La sola forma di controllo di flusso è l'istruzione di salto condizionato, che porta a scrivere programmi molto difficili da seguire logicamente per via dei continui salti da un punto all'altro del codice.
La maggior parte dei linguaggi di programmazione successivi cercarono di astrarsi da tale livello basilare, dando la possibilità di rappresentare strutture dati e strutture di controllo più generali e più vicine alla maniera (umana) di rappresentare i termini dei problemi per i quali ci si prefigge di scrivere programmi. Tra i primi linguaggi ad alto livello a raggiungere una certa popolarità ci fu il Fortran, creato nel 1957 da John Backus, da cui derivò successivamente il BASIC (1964): oltre al salto condizionato, reso con l'istruzione IF, questa nuova generazione di linguaggi introduce nuove strutture di controllo di flusso come i cicli WHILE e FOR e le istruzioni CASE e SWITCH: in questo modo diminuisce molto il ricorso alle istruzioni di salto (GOTO), cosa che rende il codice più chiaro ed elegante, e quindi di più facile manutenzione.
Dopo la comparsa del Fortran nacquero una serie di altri linguaggi di programmazione storici, che implementarono una serie di idee e paradigmi innovativi: i più importanti sono il Lisp (1959) e l'ALGOL (1960) . Tutti i linguaggi di programmazione oggi esistenti possono essere considerati discendenti da uno o più di questi primi linguaggi, di cui mutuano molti concetti di base; l'ultimo grande progenitore dei linguaggi moderni fu il Simula (1967), che introdusse per primo il concetto (allora appena abbozzato) di oggetto software. Nel 1970 Niklaus Wirth pubblica il Pascal, il primo linguaggio strutturato, a scopo didattico; nel 1972 dal BCPL nascono prima il B (rapidamente dimenticato) e poi il C, che invece fu fin dall'inizio un grande successo. Nello stesso anno compare anche il Prolog, finora il principale esempio di linguaggio logico, che pur non essendo di norma utilizzato per lo sviluppo industriale del software (a causa della sua inefficienza) rappresenta una possibilità teorica estremamente affascinante.
Con i primi mini e microcomputer e le ricerche a Palo Alto, nel 1983 vede la luce Smalltalk, il primo linguaggio realmente e completamente a oggetti, che si ispira al Simula e al Lisp: oltre a essere in uso tutt'oggi in determinati settori, Smalltalk viene ricordato per l'influenza enorme che ha esercitato sulla storia dei linguaggi di programmazione, introducendo il paradigma object-oriented nella sua prima incarnazione matura. Esempi di linguaggi object-oriented odierni sono Eiffel (1986), C++ (che esce nello stesso anno di Eiffel) e successivamente Java, classe 1995.
Tutti i linguaggi di programmazione esistenti sono definiti da un lessico, una sintassi e una semantica e possiedono:
Alcuni concetti sono poi presenti nella gran parte dei linguaggi:
Programmare in un dato linguaggio di programmazione significa generalmente scrivere uno o più semplici file di testo ASCII, chiamato codice sorgente che esprime l'algoritmo del programma tradotto nel linguaggio di programmazione. I font, i colori e in generale l'aspetto grafico sono irrilevanti ai fini della programmazione in sé: per questo i programmatori non usano programmi di videoscrittura, ma degli editor di testo (come emacs e brief) che invece offrono funzioni avanzate di trattamento testi (espressioni regolari, sostituzioni condizionali e ricerche su file multipli, possibilità di richiamare strumenti esterni ecc).
Se un dato editor è in grado di lavorare a stretto contatto con gli altri strumenti di lavoro (compilatore, linker, interprete ecc.: vedi più avanti) allora più che di semplice editor si parla di IDE o ambiente di sviluppo integrato. Va notato che alcuni linguaggi di programmazione recenti consentono anche una forma mista di programmazione, in cui alla stesura di codice sorgente ASCII si associano anche operazioni di programmazione visuale, attraverso le quali il programmatore descrive alcuni aspetti del programma disegnando a video attraverso il mouse; un'applicazione tipica di quest'ultima forma di programmazione è il disegno interattivo della GUI del programma (finestre, menù, e così via). Per essere eseguito dal processore il codice sorgente deve essere tradotto in linguaggio macchina che è il linguaggio in cui opera la macchina a livello fisico, e questo è possibile attraverso due possibili tecniche: la compilazione e l'interpretazione.
Il codice sorgente, contenente le istruzioni da eseguire e (spesso) alcuni dati noti e costanti, può essere poi eseguito passandolo a un interprete che eseguirà le istruzioni in esso contenute, il che è la prassi normale per i linguaggi di scripting; oppure può venire compilato, cioè tradotto in istruzioni di linguaggio macchina da un programma compilatore: il risultato è un file binario eseguibile (codice eseguibile) che non ha bisogno di altri programmi per andare in esecuzione, ed è anche molto più veloce di un programma interpretato. In passato, la compilazione è stata la norma per tutti i linguaggi di programmazione di uso generale; attualmente vi sono numerosi linguaggi interpretati e di uso generale, come il linguaggio Java o quelli della piattaforma .NET, che applicano un approccio ibrido fra le due soluzioni, utilizzando un compilatore per produrre del codice in un linguaggio intermedio (detto bytecode) che viene successivamente interpretato. La differenza di prestazioni tra i linguaggi interpretati e quelli compilati è stata ridotta con tecniche di compilazione just-in-time, sebbene si continui a utilizzare i linguaggi compilati (se non addirittura l'assembly) per le applicazioni che richiedono le massime prestazioni possibili.
La compilazione è il processo per cui il programma, scritto in un linguaggio di programmazione ad alto livello, viene tradotto in un codice eseguibile per mezzo di un altro programma detto appunto compilatore. La compilazione offre numerosi vantaggi, primo fra tutti il fatto di ottenere eseguibili velocissimi nella fase di run (esecuzione) adattando vari parametri di questa fase all'hardware a disposizione; ma ha lo svantaggio principale nel fatto che è necessario compilare un eseguibile diverso per ogni sistema operativo o hardware (piattaforma) sul quale si desidera rendere disponibile l'esecuzione ovvero viene a mancare la cosiddetta portabilità.
Per risolvere il problema della portabilità (la dipendenza o meno del linguaggio dalla piattaforma) sono stati creati dei linguaggi basati su librerie compilate (componenti) ad hoc per ogni piattaforma, nei quali il codice sorgente viene eseguito direttamente, quindi non c'è la necessità di una compilazione su ogni tipologia di macchina su cui viene eseguito.[senza fonte] Il difetto di questi linguaggi è la lentezza dell'esecuzione; però hanno il pregio di permettere di usare lo stesso programma senza modifica su più piattaforme. Si dice in questo caso che il programma è portabile.
La perdita di prestazioni che è alla base dei linguaggi interpretati è il doppio lavoro che è affidato alla macchina che si accinge a elaborare tale programma. Al contrario di un programma compilato, infatti, ogni istruzione viene controllata e interpretata a ogni esecuzione da un interprete. Si usano linguaggi interpretati nella fase di messa a punto di un programma per evitare di effettuare numerose compilazioni o invece quando si vuole creare software che svolgono operazioni non critiche che non necessitano di ottimizzazioni riguardanti velocità o dimensioni, ma che traggono più vantaggio dalla portabilità. I linguaggi di scripting e tutti quelli orientati al Web sono quasi sempre interpretati. PHP, Perl, Tcl/Tk e JavaScript e molti altri sono esempi concreti di interazione non vincolata alla piattaforma.
Ci sono vari tentativi per rendere i compilatori multipiattaforma creando un livello intermedio, una sorta di semi-interpretazione, come nel caso sopra menzionato di Java; d'altro canto per i linguaggi interpretati ci sono tentativi per generare delle compilazioni (o semi-compilazioni) automatiche specifiche per la macchina su cui sono eseguiti. Esistono anche strumenti per automatizzare per quanto possibile la compilazione di uno stesso programma su diverse piattaforme, ad esempio GNU autoconf/automake, che permette di realizzare una distribuzione del codice sorgente che può essere configurata e compilata automaticamente su diverse piattaforme, in genere almeno tutti gli Unix.
Se il programma, come spesso accade, usa delle librerie, o è composto da più moduli software, questi devono essere 'collegati' tra loro. Lo strumento che effettua questa operazione è detto appunto linker ("collegatore"), e si occupa principalmente di risolvere le interconnessioni tra i diversi moduli. Esistono principalmente due tipi differenti di collegamento: dinamico e statico.
Tutti i moduli del programma e le librerie utilizzate vengono incluse nell'eseguibile, che risulta grande, ma contiene tutto quanto necessario per la sua esecuzione. Se si rende necessaria una modifica a una delle librerie, per correggere un errore o un problema di sicurezza, tutti i programmi che le usano con collegamento statico devono essere ricollegati con le nuove versioni delle librerie.
Le librerie utilizzate sono caricate dal sistema operativo quando necessario (linking dinamico; le librerie esterne sono chiamate "DLL", Dynamic-link libraries nei sistemi Microsoft Windows, mentre "SO" Shared Object nei sistemi Unix-like). L'eseguibile risultante è più compatto, ma dipende dalla presenza delle librerie utilizzate nel sistema operativo per poter essere eseguito. In questo modo, le librerie possono essere aggiornate una sola volta a livello di sistema operativo, senza necessità di ricollegare i programmi. Diventa anche possibile usare diverse versioni della stessa libreria, o usare librerie personalizzate con caratteristiche specifiche per il particolare host. Nella realizzazione di un progetto software complesso, può succedere che alcune parti del programma vengano realizzate come librerie, per comodità di manutenzione o per poterle usare in diversi programmi che fanno parte dello stesso progetto.
La complicazione aggiunta è che quando si installa un programma con collegamento dinamico è necessario verificare la presenza delle librerie che utilizza, ed eventualmente installare anche queste. I sistemi di package management, che si occupano di installare i programmi su un sistema operativo, di solito tengono traccia automaticamente di queste dipendenze. In genere si preferisce il collegamento dinamico, in modo da creare programmi piccoli e in generale ridurre la memoria RAM occupata, assumendo che le librerie necessarie siano già presenti nel sistema, o talvolta distribuendole insieme al programma.
Questi due metodi di creazione ed esecuzione di un programma presentano entrambi vantaggi e svantaggi: il maggior vantaggio della compilazione è senz'altro l'efficienza nettamente superiore in termini di prestazioni, al prezzo del restare vincolati a una piattaforma (combinazione di architettura hardware e sistema operativo) particolare; un linguaggio interpretato invece non ha, in linea di massima, questa dipendenza ma è più lento e richiede più memoria in fase di esecuzione.
Una soluzione intermedia fra compilazione e interpretazione è stata introdotta nelle prime versioni di Pascal (compresa quella realizzata nel 1975 dal suo inventore, Niklaus Wirth) e successivamente adottata nei linguaggi Java e Python, con il bytecode, e nei linguaggi Visual Basic e .NET di Microsoft con il P-code.
In tutti e due questi casi il codice sorgente dei programmi non viene compilato in linguaggio macchina, ma in un codice intermedio "ibrido" destinato a venire interpretato al momento dell'esecuzione del programma: il motivo di questo doppio passaggio è di avere la portabilità dei linguaggi interpretati ma anche, grazie alla pre-compilazione, una fase di interpretazione più semplice e quindi più veloce. Nel caso del bytecode di Java siamo di fronte a un vero linguaggio assembly, che in origine doveva essere implementato in un modello di processore reale, poi mai realizzato; alcuni microprocessori moderni, come gli ARM con Jazelle implementano nativamente molte istruzioni bytecode e sono quindi in grado di eseguire bytecode Java come fosse assembly.
Tuttavia il codice intermedio è più facile sia da interpretare che da compilare: per questo motivo sia per Java che per i linguaggi .NET sono stati sviluppati i compilatori JIT (Just In Time), che al momento del lancio di un programma Java o .NET compilano al volo il codice intermedio e mandano in esecuzione un codice macchina nativo, eliminando completamente la necessità dell'interprete e rendendo i programmi scritti in questi linguaggi veloci quasi quanto i corrispondenti programmi compilati.
Con ambiente di sviluppo si intendono l'insieme degli strumenti atti allo sviluppo del codice sorgente del programma, mentre con ambiente di esecuzione si intende tipicamente il complesso delle librerie software, detta anche piattaforma software, utilizzate dal programma stesso per poter funzionare correttamente.
In generale esistono circa 2500 linguaggi di programmazione più o meno noti e diffusi. Questi in primis vengono classificati, a seconda del livello di astrazione a partire dal linguaggio macchina fin verso il linguaggio logico umano, in linguaggi a basso livello e ad alto livello (negli anni novanta si distinguevano anche quelli ad altissimo livello). A loro volta i linguaggi possono essere classificati in linguaggi compilati e interpretati come visto sopra. Normalmente i linguaggi vengono poi distinti in tre grandi famiglie basate sul paradigma di programmazione di riferimento: i linguaggi imperativi, quelli funzionali e quelli logici.
Nei linguaggi imperativi l'istruzione è un comando esplicito, che opera su una o più variabili oppure sullo stato interno della macchina, e le istruzioni vengono eseguite in un ordine prestabilito. Scrivere un programma in un linguaggio imperativo significa essenzialmente occuparsi di cosa la macchina deve fare per ottenere il risultato che si vuole, e il programmatore è impegnato nel mettere a punto gli algoritmi necessari a manipolare i dati. Le strutture di controllo assumono la forma di istruzioni di flusso (GOTO, FOR, IF/THEN/ELSE ecc.) e il calcolo procede per iterazione piuttosto che per ricorsione. I valori delle variabili sono spesso assegnati a partire da costanti o da altre variabili (assegnamento) e raramente per passaggio di parametri (istanziazione).
Tipici linguaggi imperativi:
La programmazione strutturata è una tecnica il cui scopo è di limitare la complessità della struttura del controllo dei programmi. Il programmatore è vincolato a usare solo le strutture di controllo canoniche definite dal Teorema di Böhm-Jacopini, ovvero la sequenza, la selezione e il ciclo, evitando le istruzioni di salto incondizionato.
La programmazione a oggetti è basata su un'evoluzione del concetto di tipo di dato astratto caratterizzata da incapsulamento, ereditarietà, polimorfismo. Oltre a linguaggi specializzati che implementano completamente i principi di tale metodologia (come Smalltalk o Java), molti linguaggi moderni incorporano alcuni concetti della programmazione a oggetti.
I linguaggi funzionali sono basati sul concetto matematico di funzione. In un linguaggio funzionale puro l'assegnazione esplicita risulta addirittura completamente assente e si utilizza soltanto il passaggio dei parametri. Tipicamente in tale modello il controllo del calcolo è gestito dalla ricorsione e dal pattern matching (l'azione di controllo della presenza di un certo motivo — pattern — all'interno di un oggetto composito), mentre la struttura dati più diffusa è la lista, una sequenza di elementi. Il più importante esponente di questa categoria è senz'altro il Lisp (LISt Processing).
Nei linguaggi logici l'istruzione è una clausola che descrive una relazione fra i dati: programmare in un linguaggio logico significa descrivere l'insieme delle relazioni esistenti fra i dati e il risultato voluto, e il programmatore è impegnato nello stabilire in che modo i dati devono evolvere durante il calcolo. Non c'è un ordine prestabilito di esecuzione delle varie clausole, ma è compito dell'interprete trovare l'ordine giusto. La struttura di controllo principale è rappresentata dal cut, che è detto rosso se modifica il comportamento del programma o verde se rende solo più efficiente il calcolo, che procede per ricorsione e non per iterazione. Le variabili ricevono il loro valore per istanziazione o da altre variabili già assegnate nella clausola (unificazione) e quasi mai per assegnamento, che è usato solo in caso di calcolo diretto di espressioni numeriche.
Affinché sia possibile usarli in un programma dichiarativo, tutti i normali algoritmi devono essere riformulati in termini ricorsivi e di backtracking; questo rende la programmazione con questi linguaggi un'esperienza del tutto nuova e richiede di assumere un modo di pensare radicalmente diverso, perché più che calcolare un risultato si richiede di dimostrarne il valore esatto. A fronte di queste richieste, i linguaggi dichiarativi consentono di raggiungere risultati eccezionali quando si tratta di manipolare gruppi di enti in relazione fra loro.
Un'altra classificazione vuole dal punto di vista dei tipo di dato espresso vuole la suddivisione in linguaggi a tipizzazione forte o a tipizzazione debole.
I moderni supercomputer e — ormai — tutti i calcolatori di fascia alta e media sono equipaggiati con più CPU. Come ovvia conseguenza, questo richiede la capacità di sfruttarle; per questo sono stati sviluppati dapprima il multithreading, cioè la capacità di lanciare più parti dello stesso programma contemporaneamente su CPU diverse, e in seguito alcuni linguaggi studiati in modo tale da poter individuare da soli, in fase di compilazione, le parti di codice da lanciare in parallelo.
I linguaggi di scripting sono nati come linguaggi batch, per automatizzare compiti lunghi e ripetitivi da eseguire, appunto, in modalità batch. Invece di digitare uno a uno i comandi per realizzare un certo compito, essi sono salvati in sequenza in un file, utilizzabile a sua volta come comando composto. I primi linguaggi di scripting sono stati quelli delle shell Unix; successivamente, vista l'utilità del concetto, molti altri programmi interattivi hanno cominciato a permettere il salvataggio e l'esecuzione di file contenenti liste di comandi, oppure il salvataggio di registrazioni di comandi visuali (le cosiddette macro dei programmi di videoscrittura, per esempio). Il passo successivo, è stato in molti casi l'estensione dei linguaggi con l'associazione di simboli a valori, cioè l'uso di variabili, con i comandi di gestione del flusso, ovvero i costrutti di salto condizionato, le istruzioni di ciclo o di ricorsione, rendendoli così linguaggi completi. Recentemente molti programmi nati per scopi ben diversi dalla programmazione offrono agli utenti la possibilità di programmarli in modo autonomo tramite linguaggi di scripting.
La sintassi di molti linguaggi di scripting, come PHP o i dialetti di ECMAScript, è simile a quella del C, mentre altri, come Perl o Python, ne adottano invece una progettata ex novo. Visto che molto spesso i linguaggi di scripting nascono per l'invocazione di comandi o procedure esterne, altrettanto spesso essi sono interpretati, cioè eseguiti da un altro programma, come il programma madre, del quale il linguaggio di scripting è un'estensione, o un apposito interprete.
Altri tipi di linguaggi sono i linguaggi di programmazione ad altissimo livello utilizzato da professionisti e i linguaggi di programmazione visuali che non richiedono particolari conoscenze avanzate in fatto di programmazione.
Non ha senso, in generale, parlare di linguaggi migliori o peggiori, o di linguaggi migliori in assoluto: ogni linguaggio nasce per affrontare una classe di problemi più o meno ampia, in un certo modo e in un certo ambito. Però, dovendo dire se un dato linguaggio sia adatto o no per un certo uso, è necessario valutare le caratteristiche dei vari linguaggi.
Sono le qualità del linguaggio in sé, determinate dalla sua sintassi e dalla sua architettura interna. Influenzano direttamente il lavoro del programmatore, condizionandolo. Non dipendono né dagli strumenti usati (compilatore/interprete, IDE, linker) né dal sistema operativo o dal tipo di macchina.
A volte, un programma molto complesso e poco leggibile in un dato linguaggio può diventare assolutamente semplice e lineare se riscritto in un linguaggio di classe differente, più adatta.
L'esempio più comune di linguaggio robusto è il Pascal, che essendo nato a scopo didattico presuppone sempre che un'irregolarità nel codice sia frutto di un errore del programmatore; mentre l'assembly è l'esempio per antonomasia di linguaggio totalmente libero, in cui niente vincola il programmatore (e se scrive codice pericoloso o errato, non c'è niente che lo avverta).
Oltre alle accennate qualità dei linguaggi, possono essere esaminate quelle degli ambienti in cui operano. Un programmatore lavora con strumenti software, la cui qualità e produttività dipende da un insieme di fattori che vanno pesati anch'essi in funzione del tipo di programmi che si intende scrivere.
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.