Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


informatica di base, sintesi del corso, Appunti di Fondamenti di informatica

basi dell'informatica utili per comprendere il linguaggio di programmazione ecc

Tipologia: Appunti

2021/2022

Caricato il 29/03/2023

elisa-cercaci
elisa-cercaci 🇮🇹

4.3

(4)

19 documenti

1 / 86

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
INFORMATICA
1. Che cos’è un computer?
Il termine computer (calcolatore, in italiano) è in uso, oggi, per indicare una
“macchina per l'elaborazione di dati rappresentati da caratteri alfanumerici
variamente codificati, che vengono sottoposti a procedimenti aritmetici e logici,
memorizzati in archivi e resi reperibili e trasmissibili” – dal%dizionario di
Repubblica.it. Tuttavia, la definizione originale dello stesso termine, in uso dal
diciassettesimo secolo, è leggermente differente, visto che si riferisce a qualcuno
che “esegue calcoli matematici” – da%Wikipedia. In questo capitolo, quando
useremo il termine “computer”, considereremo sempre la sua accezione più
generica, ovvero: qualsiasi agente (ovvero, quell’entità in grado di agire se istruita
appropriatamente, come una persona o una macchina) che è in grado di fare
calcoli e produrre una risposta (detta output) a partire da qualche informazione
iniziale (detta input).
Computer umani, ovvero gruppi di persone che hanno eseguito lunghi calcoli per
determinati esperimenti, sono stati impiegati molte volte nel passato. Per esempio,
in astronomia, computer umani sono stati impiegati per calcolare le coordinate
astronomiche di oggetti extraterrestri – come i calcoli effettuati da Alexis Claude
Clairaut e colleghi per comprendere i vari passaggi della%cometa di Halley. Come
ulteriore esempio, computer umani sono stati usati anche da Napoleone Bonaparte
quanto questo ha imposto la creazione di tabelle matematiche per convertire i
valori descritti con il vecchio sistema di misura imperiale verso il nuovo sistema
metrico (tutt’ora in uso) [Campbell-Kelly, 2009] [Roegel, 2010].
Nel 1822,% Charles Babbage , capendo la complessità di eseguire tutti questi calcoli
a mano evitando l’introduzione di errori, iniziò lo sviluppo di un nuova, incredibile,
macchina, chiamata%Macchina Differenziale, mostrata in Figura 1. L’idea era quella
di avere a disposizione una macchina che potesse gestire operazioni simili a quelle
effettuate dai computer umani, ma in modo che fossero eseguite
automaticamente, velocemente, e senza errori. Babbage fu in grado di costruire
solo un prototipo parziale della macchina e, dopo l’entusiasmo iniziale, fu
demoralizzato dalla limitata flessibilità che offriva. Infatti, la Macchina DIfferenziale
non era programmabile e, di conseguenza, era in grado di utilizzare solo un
numero limitato di operazioni sull’input ricevuto – specificato, fisicamente,
cambiando specifiche configurazioni della macchina.
In modo da sopperire a queste limitazioni, nel 1837, Babbage iniziò a progettare
una nuova macchina, la%Macchina Analitica, mostrata in Figura 2. Seppur nessun
prototipo di questa macchina sia stato effettivamente costruito da Babbage, in
linea di principio avrebbe dovuto permettere la creazione di qualunque calcolo
procedurale, rendendola il primo computer meccanico e general-purpose della
storia. Contrariamente al suo predecessore, la Macchina Analitica era in grado di
ricevere in input istruzioni e dati mediante l’uso di%schede perforate, senza
obbligare l'utilizzatore, quindi, a compiere manipolazioni fisiche della macchina
stessa per farla funzionare.
C’è voluto più di un secolo per vedere sviluppate in una macchina fisica le idee
presentate nella Macchina Analitica. Infatti, l’evoluzione della tecnologia
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d
pf2e
pf2f
pf30
pf31
pf32
pf33
pf34
pf35
pf36
pf37
pf38
pf39
pf3a
pf3b
pf3c
pf3d
pf3e
pf3f
pf40
pf41
pf42
pf43
pf44
pf45
pf46
pf47
pf48
pf49
pf4a
pf4b
pf4c
pf4d
pf4e
pf4f
pf50
pf51
pf52
pf53
pf54
pf55
pf56

Anteprima parziale del testo

Scarica informatica di base, sintesi del corso e più Appunti in PDF di Fondamenti di informatica solo su Docsity!

INFORMATICA

1. Che cos’è un computer?

Il termine computer (calcolatore, in italiano) è in uso, oggi, per indicare una “macchina per l'elaborazione di dati rappresentati da caratteri alfanumerici variamente codificati, che vengono sottoposti a procedimenti aritmetici e logici, memorizzati in archivi e resi reperibili e trasmissibili” – dal dizionario di Repubblica.it. Tuttavia, la definizione originale dello stesso termine, in uso dal diciassettesimo secolo, è leggermente differente, visto che si riferisce a qualcuno che “esegue calcoli matematici” – da Wikipedia. In questo capitolo, quando useremo il termine “computer”, considereremo sempre la sua accezione più generica, ovvero: qualsiasi agente (ovvero, quell’entità in grado di agire se istruita appropriatamente, come una persona o una macchina) che è in grado di fare calcoli e produrre una risposta (detta output) a partire da qualche informazione iniziale (detta input). Computer umani, ovvero gruppi di persone che hanno eseguito lunghi calcoli per determinati esperimenti, sono stati impiegati molte volte nel passato. Per esempio, in astronomia, computer umani sono stati impiegati per calcolare le coordinate astronomiche di oggetti extraterrestri – come i calcoli effettuati da Alexis Claude Clairaut e colleghi per comprendere i vari passaggi della cometa di Halley. Come ulteriore esempio, computer umani sono stati usati anche da Napoleone Bonaparte quanto questo ha imposto la creazione di tabelle matematiche per convertire i valori descritti con il vecchio sistema di misura imperiale verso il nuovo sistema metrico (tutt’ora in uso) [Campbell-Kelly, 2009] [Roegel, 2010]. Nel 1822, Charles Babbage, capendo la complessità di eseguire tutti questi calcoli a mano evitando l’introduzione di errori, iniziò lo sviluppo di un nuova, incredibile, macchina, chiamata Macchina Differenziale, mostrata in Figura 1. L’idea era quella di avere a disposizione una macchina che potesse gestire operazioni simili a quelle effettuate dai computer umani, ma in modo che fossero eseguite automaticamente, velocemente, e senza errori. Babbage fu in grado di costruire solo un prototipo parziale della macchina e, dopo l’entusiasmo iniziale, fu demoralizzato dalla limitata flessibilità che offriva. Infatti, la Macchina DIfferenziale non era programmabile e, di conseguenza, era in grado di utilizzare solo un numero limitato di operazioni sull’input ricevuto – specificato, fisicamente, cambiando specifiche configurazioni della macchina. In modo da sopperire a queste limitazioni, nel 1837, Babbage iniziò a progettare una nuova macchina, la Macchina Analitica, mostrata in Figura 2. Seppur nessun prototipo di questa macchina sia stato effettivamente costruito da Babbage, in linea di principio avrebbe dovuto permettere la creazione di qualunque calcolo procedurale, rendendola il primo computer meccanico e general-purpose della storia. Contrariamente al suo predecessore, la Macchina Analitica era in grado di ricevere in input istruzioni e dati mediante l’uso di schede perforate, senza obbligare l'utilizzatore, quindi, a compiere manipolazioni fisiche della macchina stessa per farla funzionare. C’è voluto più di un secolo per vedere sviluppate in una macchina fisica le idee presentate nella Macchina Analitica. Infatti, l’evoluzione della tecnologia

computazionale ha avuto una brusca accelerata soltanto a seguito della Seconda Guerra Mondiale. In quei tempi, molti calcolatori furono costruiti per ragioni militari, come, ad esempio, la Bomba (1940) (Figura 3), sviluppata da Alan Turing, che è stata il principale strumento che ha permesso a un gruppo di persone, rinchiuse nella base militare segreta britannica di Bletchley Park, di decifrare (grazie anche al lavoro pregresso fatto da crittologi polacchi come Marian Rejewski) le comunicazioni tedesche che erano state cifrate dalla macchina Enigma. Mentre la Bomba era una macchina estremamente efficace ed efficiente, era altresì parzialmente basata su componenti prettamente meccanici, e permetteva lo svolgimento di una sola operazione, anche se estremamente cruciale da un punto di vista squisitamente storico. Il primo computer interamente digitale, come pensato da Babbage con la sua Macchina Analitica, è stato sviluppato negli Stati Uniti d’America soltanto qualche anno dopo, nel 1946: l’Electronic Numerical Integrator and Computer (ENIAC), mostrato in Figura 4, che era completamente programmabile attraverso l’uso di cavi e interruttori. Questa invenzione è stata una delle più cruciali pietre miliari della storia dei computer elettronici, rappresentando una sorta di “punto fisso” nel tempo da cui tutti i moderni computer sono poi stati creati.

2. Pensiero computazionale

Spesso diciamo di programmare un computer – dove la parola “computer”, in questo caso, si riferisce a un computer elettronico. Tuttavia, come anticipato nella definizione che abbiamo fornito in questo capitolo, un computer può essere sia una macchina sia un essere umano. Tuttavia, il verbo programmare non si presta particolarmente bene ad essere usato con computer umani – visto che non possiamo letteralmente programmare una persona (anche se qualche pseudoscienza, come la programmazione neuro-linguistica, sostiene di poterlo fare). Casomai diciamo che parliamo con una persona per istruirla sull’esecuzione di specifiche azioni attraverso l’uso di un particolare linguaggio (in questo caso naturale) che viene usato come canale di comunicazione. Di conseguenza, in questo contesto, si dovrebbero usare gli stessi verbi, ovvero parlare e istruire, anche quando ci si riferisce ad un computer elettronico perché, di fatto, è quello che succede. In pratica, scrivere un programma è esattamente questo: comunicare ad un computer elettronico utilizzando un linguaggio (in questo caso formale) che sia l’istruttore umano sia il computer stesso possano comprendere [Papert, 1980]. Una volta d’accordo su quale linguaggio usare per la comunicazione tra l’istruttore e il computer (a prescindere dal fatto che questo sia un umano o una macchina), dovremmo iniziare a pensare una sequenza di possibili istruzioni da comunicare che, se seguite sistematicamente, possano restituire un risultato atteso per risolvere un certo problema. In modo da raggiungere questo obiettivo, solitamente (e inconsapevolmente) proviamo a ricercare possibili soluzioni per il problema in questione confrontandolo con possibili situazioni che si sono già presentate in passato – e che, plausibilmente, abbiamo già risolto. L’idea è quella di trovare un pattern (schema ricorrente) che fornisca una possibile soluzione a un insieme di situazioni che, a livello astratto, sono del tutto omogenee, in modo da poter riusare la stessa strategia per raggiungere il nostro obiettivo, se questa è stata soddisfacente nel passato. Per esempio, alcune delle azioni che eseguiamo in un ufficio postale sono abbastanza simili a quelle che eravamo abituati a svolgere

astrazioni, dobbiamo ridefinirle usando un linguaggio appropriato per renderle comprensibili a un computer. Questo richiede a volte una notevole capacità di introspezione e di riflessione sulle caratteristiche più importanti di un oggetto, di un discorso o di una situazione, non troppo diversamente da ciò che avviene nel pensiero filosofico o semiotico, ma sempre garantendo precisione e adeguatezza alla risoluzione di problemi e alla rappresentazione del loro contenuto. Questo indipendentemente dal fatto che vogliamo risolvere il problema di come elaborare un testo digitale, di come organizzare i dati relativi a un’indagine archeologica, o di come trovare il modo migliore per raccomandare un prodotto o un servizio. A livello generale, l’obiettivo principale (dell’insegnamento) del pensiero computazionale è quello di permettere alle persone di pensare come se fossero computer scientist, anche quando bisogna affrontare attività del quotidiano. In futuro, il pensiero computazionale sarà parte integrante dell’educazione primaria delle persone [Wing, 2008], come la matematica e la fisica, e plasmerà il modo in cui le persone pensano e imparano [Papert, 1980] e di conseguenza, vivono. In modo indiretto, questo sta già avvenendo attraverso la trasformazione dei sistemi di produzione e comunicazione: il Web e i social media, gli “orchestratori di reti” (come AirBnB e Uber), etc. Categorie fondamentali dell’esistenza umana: spazio, tempo, identità, immagine pubblica, collettività, pianificazione, relazioni interpersonali, etc. sono tutte influenzate dalla trasformazione socio-tecnica in maniera più veloce e profonda di quanto sia avvenuto negli ultimi secoli.

3. Strutture dati

Uno dei processi di base dell’attività di astrazione è quella di descrivere l’informazione presente in una certa situazione secondo un’organizzazione generica e riutilizzabile in più contesti. Per fare ciò, si usano quelle che comunemente sono chiamate strutture dati. Le strutture dati sono i modi in cui possiamo organizzare l’informazione e i dati da essere processati (input) e restituiti (output) da un computer, in modo da potervi accedere in modo efficiente ed efficace a livello computazionale. In pratica, una struttura dati è una sorta di contenitore dove possiamo posizionare alcune informazioni, e che fornisce dei metodi specifici per aggiungere e richiedere pezzi di questa informazione. Tra le più semplici strutture dati abbiamo: le liste, le code, le pile, gli insiemi, i dizionari, gli alberi e i grafi. Tutte queste strutture verranno analizzate nelle sottosezioni che seguono, fornendo esempi delle loro applicazioni in scenari quotidiani.

Lista

Una lista è una sequenza di elementi ordinati e ripetibili che si possono contare, perché si può sapere quanti elementi essa contiene in un dato momento. I suoi elementi sono ordinati perché sono posizionati in uno specifico ordine di precedenza tra loro, che è preservato anche quando aggiungiamo e rimuoviamo determinati elementi. Inoltre, gli elementi in una lista sono ripetibili, visto che possono comparire più di una volta in una lista – ad esempio, se proviamo a creare una lista di caratteri della parola “pensiero”, la lettera “e” comparirà due volte, una in seconda posizione, e un’altra in sesta posizione.

Esistono diversi esempi tratti da situazioni reali di queste liste astratte. Per esempio, in Figura 7, sono mostrati un indice di un libro e una lista di riferimenti bibliografici in un articolo scientifico. Entrambi sono due oggetti concreti costruiti partendo dalla nozione astratta di lista. Pila Una pila è una specie di lista vista da un particolare punto di vista, ovvero dal basso verso l’alto, e con uno specifico insieme di operazioni che si possono effettuare sugli elementi della pila. Figura 8 mostra due esempi di pile in oggetti di tutti i giorni. In particolare, abbiamo una pila di sedie (a sinistra) e una pila di libri (a destra). La caratteristica principale degli elementi di questa struttura riguarda le operazioni di aggiunta e rimozione, che seguono una strategia last in first out strategy (LIFO)

  • ovvero, l’ultimo elemento che viene inserito è il primo ad essere rimosso. Infatti, l’ultimo elemento inserito nella struttura è posizionato in cima alla pila e, quindi, è anche il primo che verrà rimosso se richiesto. Inoltre, se si vuole rimuovere un elemento in mezzo alla pila, è prima necessario rimuovere tutti gli elementi che sono stati posizionati dopo questo, dal più recente al più vecchio.

Coda

Una coda è una specie di lista vista da un’altra prospettiva, ovvero da sinistra verso destra, e con uno specifico insieme di operazioni che possono essere effettuate sugli elementi che contiene. Figura 9 mostra due differenti esempi di code in situazioni del quotidiano: una coda di bambini (sinistra) e una linea di attesa di taxi (destra). La caratteristica principale degli elementi di questa struttura riguarda le operazioni di aggiunta e rimozione degli elementi, che seguono una strategia first in first out strategy (FIFO) – ovvero, il primo elemento che viene aggiunto è anche il primo che viene rimosso a seguito di una richiesta. In pratica, il primo elemento inserito nella struttura è posizionato nella parte libera più a sinistra della coda e, di conseguenza, è anche il primo che verrà rimosso quando richiesto. In modo del tutto simile alle pile, anche nelle code, se si vuole rimuovere un certo elemento in mezzo, è necessario prima rimuovere tutti gli elementi che sono stati aggiunti prima di esso.

Insieme

Un insieme è una collezione di elementi non ordinati e non ripetibili che si possono contare. I suoi elementi non sono ordinati perché l’ordine di inserimento di questi non prescrive nessuna relazione di cardinalità tra loro. Inoltre, sono non ripetibili perché lo stesso valore non può essere incluso due o più volte. Ovviamente, esistono diversi esempi di questa struttura dati in oggetti e situazioni di vita quotidiana. Per esempio, Figura 10 mostra una classe di studenti e una collezione di colori. Entrambe sono oggetti concreti costruiti a partire dalla nozione astratta di insieme. Dizionario

modo astratto: un albero – come mostrato in Figura 13. Un albero è una struttura dati composta da un insieme di nodi collegati tra loro da una relazione gerarchica genitore-figlio. Come mostrato in Figura 14, i nodi di questa struttura dati vengono disposti dall’alto verso il basso, contrariamente all’organizzazione dell’albero che siamo abituati ad osservare in natura. Figure 14. Un albero con la tipica nomenclatura dei suoi nodi a partire dal nodo centrale evidenziato in giallo. In questa figura, il bordo in grassetto è usato per identificare il nodo radice dell’albero (root node), che è l’unico senza alcun genitore, mentre il bordo tratteggiato è usato per indicare i nodi foglia dell’albero (leaf nodes), ovvero i nodi sprovvisti di figli. Il nodo di origine, ovvero quello più in alto, è chiamato nodo radice (root node). Invece, i nodi che terminano l’albero, chiamati nodi foglia (leaf nodes), sono posizionati in basso nell’albero, rispetto al nodo radice. Prendendo in considerazione uno specifico nodo dell’albero, come quello evidenziato in giallo in Figura 14, possiamo definire tutti i restanti nodi come segue:  il nodo genitore (parent node) è quello direttamente connesso quando ci si muove verso il nodo radice;  un nodo figlio (child node) è uno di quelli direttamente connessi quando ci si muove lontano dal nodo radice;  un nodo fratello (sibling node) è uno di quelli che condivide lo stesso nodo genitore;

 un nodo antenato (ancestor node) è uno di quelli raggiungibili seguendo ripetutamente le relazioni genitore-figlio andando verso il nodo radice;  un nodo discendente (descendant node) è uno di quelli raggiungibili seguendo ripetutamente le relazioni genitore-figlio spostandosi lontano dal nodo radice. Figura 15. Un “albero di analisi a costituenti” della frase “Alice was beginning to get very tired of sitting by her sister on the bank”. Sorgente: CoreNLP parser.

Algoritmi e computabilità

1. Usi possibili della Macchina Analitica

Ada Lovelace (Figura 1) era la figlia del poeta Lord Byron. Matematica di formazione, è diventata famosa per il suo lavoro sulla Macchina Analitica di Babbage. La madre, Anne Isabella Milbanke, In contrasto con le abitudini del padre, che mal sopportava il suo interesse per la cultura scientifica, aveva invece da sempre supportato l’interesse che Ada aveva nella logica e nella matematica. Uno degli obiettivi della madre, infatti, era quello di evitare che la figlia incorresse nella stessa sregolatezza emozionale ed esistenziale che aveva caratterizzato la vita del padre. Tuttavia, in qualche modo la creatività insita nella famiglia venne poi manifestata in modi assolutamente imprevedibili. Nel 1833, Ada partecipò ad una festa organizzata da Charles Babbage per presentare la Macchina Differenziale. Fu talmente colpita dall’invenzione di Babbage che iniziò una corrispondenza epistolare con lui che durò 27 anni [Morais, 2013]. Ada fu la traduttrice in inglese del primissimo articolo sulla Macchina Analitica, scritto da Luigi Federico Menabrea, e che lei stessa arricchì con un grande numero di annotazioni personali e riflessioni. Tra queste, c’era anche una descrizione di come usare la Macchina Analitica per calcolare i numeri di Bernoulli [Menabrea, 1842]. Tecnicamente, questo fu il primo programma – nonché il primo algoritmo – per un computer digitale mai scritto, e fu creato da Ada senza avere neppure a disposizione la macchina reale, visto che la Macchina Analitica era soltanto una macchina teorica che Babbage non costruì mai. Tuttavia, la sua visione sui possibili usi della Macchina Analitica andava anche oltre [Morais, 2013]: <<The operating mechanism can even be thrown into action independently of any object to operate upon (although of course no result could then be developed). Again, it might act upon other things besides number, were objects found whose mutual fundamental relations could be expressed by those of the abstract science of operations, and which should be also susceptible of adaptations to the action of the operating notation and mechanism of the engine. Supposing, for instance, that the fundamental relations of pitched sounds in the science of harmony and of musical composition were susceptible of such expression and adaptations, the engine might compose elaborate and scientific pieces of music of any degree of complexity or extent.>> [Traduzione libera in italiano: “Il meccanismo può anche essere messo al lavoro indipendentemente dalla presenza effettiva di oggetti su cui operare (benché ovviamente in questo caso non arrivi necessariamente a un risultato). Inoltre, potrebbe operare su altre cose oltre ai numeri, se si trovassero oggetti le cui relazioni fondamentali possano essere espresse da quelle della scienza astratta delle operazioni, e che dovrebbero essere adattate all’azione della notazione delle operazioni e ai meccanismi della macchina. Supponendo, per esempio, che le relazioni fondamentali tra i suoni di varia altezza, nella scienza dell’armonia e della composizione musicale, siano suscettibili di tali espressioni e adattamenti, la macchina potrebbe comporre scientificamente brani di musica elaborati di qualunque durata o grado di complessità.”]

Quel “science of operations” indicato nel testo è un riferimento ad uno specifico campo scientifico che fu chiaramente identificato soltanto molti anni dopo. In pratica, Ada Lovelace stava parlando dell’Informatica ben cent’anni prima della sua introduzione formale. Per il suo lavoro nel campo, Ada Lovelace è spesso riconosciuta come il primo programmatore della storia.

2. Cos’è un algoritmo?

Gli algoritmi accompagnano sistematicamente le nostre attività della vita quotidiana. Per esempio, in Figura 2 sono mostrati due esempi di procedure passo passo che dobbiamo seguire, rispettivamente, per preparare salatini e per assemblare una specifica lampada. Mentre l’obiettivo dei due esempi è estremamente diverso, in quanto nel primo è una ricetta e nel secondo è un insieme di istruzioni per assemblare un utensile, essi sono descritti nei termini della stessa nozione astratta: istruzione per produrre qualcosa partendo da un qualche materiale iniziale a disposizione – che, di fatto, rispecchia pienamente la definizione di algoritmo. La parola algoritmo è una combinazione della parola latina algorismus (che, a sua volta, è la latinizzazione del nome Al-Khwarizmi, che era un grande matematico persiano dell’ottavo secolo) e della parola greca arithmos, che significa numero. A livello generale, possiamo definire un algoritmo come l’astrazione di una procedura passo passo che prende qualcosa come input e produce un certo output [Wing, 2008]. Ogni algoritmo è scritto in un linguaggio specifico in modo che le istruzioni che definisce possano essere comunicate e comprese da un computer (sia esso umano o macchina) in modo da ottenere qualcosa come conseguenza dell’elaborazione di qualche materiale di input. Un programmatore è una persona che crea algoritmi e li specifica in programmi usando uno specifico linguaggio comprensibile dal computer – ove, in questo caso, il termine computer si riferisce ai computer elettronici. Tuttavia, se ci si astrae dalla nozione di programma, un programmatore è chiunque sia in grado di creare algoritmi che possono essere interpretati da un qualunque computer (sia esso umano o macchina).

3. Diagrammi di flusso

Non esiste un linguaggio standard per descrivere un algoritmo in modo che possa essere immediatamente comprensibile da un qualunque computer. Tuttavia, spesso gli informatici si basano su uno pseudocodice quando vogliono descrivere un particolare algoritmo. Uno pseudocodice è un linguaggio informale che può essere facilmente interpretato da un qualunque computer, anche se è solitamente usato per comunicare i passi principali di un algoritmo ad un umano. Mentre un algoritmo descritto mediante l’uso di pseudocodice non è eseguibile da un computer elettronico, i suoi costrutti sono strettamente connessi con quelli tipicamente definiti nei linguaggi di programmazione. In particolare, ogni algoritmo può essere espresso in pseudocodice e, in principio, questo può essere a sua volta tradotto, abbastanza facilmente, in diversi linguaggi

dell’algoritmo), mentre il terminale di fine può essere raggiunto da differenti punti dell’algoritmo, e quindi è collegato da almeno una freccia. La prima versione incompleta dell’algoritmo, mostrata di seguito, semplifica un poco le istruzioni in linguaggio naturale precedentemente introdotte, in modo da mostrare come possiamo usare alcuni iniziale oggetti per creare un algoritmo, senza aggiungere ulteriore complessità, almeno per il momento. In particolare, la versione semplificata prende in input solo due stringhe, una parola e un riferimento bibliografico, e restituisce 1 se la parola è contenuta nel riferimento bibliografico, 0 altrimenti. Questa versione parziale è mostrata nel diagramma di flusso in Figura 3. Figure 3. L’algoritmo incompleto descritto da un semplice diagramma di flusso. Questa versione parziale usa già molti degli oggetti grafici propri ai diagrammi di flusso. In particolare, oltre ai terminali di inizio e fine, abbiamo usato tre oggetti di input / output per acquisire i valori specificati come input e per restituire 0 o 1 dipendentemente da questo input. La decisione su quale output restituire è stata codificata grazie all’oggetto decisionale dei diagrammi di flusso, in cui l’input è analizzato e, a seconda della situazione, uno specifico ramo del flusso dell’algoritmo viene percorso.

L’algoritmo completo

Mentre nella sezione precedente è stata introdotta una prima implementazione della versione parziale dell’algoritmo, l’implementazione dell’algoritmo completo attraverso lo sviluppo di un diagramma di flusso è mostrata in Figura 4. In questo caso, sono stati utilizzati tutti gli oggetti grafici introdotti in Tabella 1. Tuttavia, è importante sottolineare come il diagramma di flusso presentato è soltanto un possibile modo per implementare l’algoritmo originale. Infatti, è possibile creare anche un diagramma di flusso diverso che, però, risolve il problema descritto dall’algoritmo in linguaggio naturale correttamente. Nel diagramma in Figura 4, viene utilizzato il primo oggetto di processo in cui viene inizializzato a 0, associandolo implicitamente a una variabile (ovvero, “result value” in figura), il risultato che verrà restituito alla fine dell’esecuzione dell’algoritmo. Questo risultato è quello che l’algoritmo deve ritornare se entrambe

le parole in input non sono contenute nel riferimento bibliografico specificato. Questo oggetto di processo è seguito da due oggetti decisionali messi in sequenza, che controllano le due condizioni – ovvero se la prima parola è contenuta nel riferimento bibliografico, e se la seconda parola è contenuta nello stesso riferimento – e, nel caso queste siano vere, eseguono un incremento di 1 al risultato finale da restituire, mediante l’uso di altri oggetti di processo. Alla fine, qualunque sia il valore che è stato associato al risultato finale viene restituito da un unico oggetto di output, che conclude l’esecuzione dell’algoritmo. Figura 4. Il diagramma di flusso che implementa l’algoritmo completo.

5. Ci sono limiti alla computazione?

Una delle domande tradizionali che le persone, che si avvicinano al pensiero computazionale e agli algoritmo, di solito si pongono è: possiamo usare gli algoritmi per computare qualsiasi cosa vogliamo? In altre parole: esiste un limite a quello che possiamo computare? O ancora: è possibile definire un problema computazionale – ovvero un problema che può essere risolto algoritmicamente da un computer – che non può essere risolto da nessun algoritmo? Nel caso dell’informatica, ma anche di tutte le scienze matematiche, uno degli approcci più usati per dimostrare che qualcosa non esiste è quello di costruire una situazione in apparenza plausibile che, poi, si rivela paradossale e auto- contraddittoria – in cui, per esempio, l’esistenza di un algoritmo contraddice se stessa. Questo approccio dimostrativo porta il nome di reductio ad absurdum (dimostrazione per assurdo). L’argomentazione che ne sta alla base è quella di stabilire che una situazione è contraddittoria cercando di derivare un’assurdità dalla sua negazione, in modo da dimostrare che una tesi deve essere accettata perché la sua negazione non può essere difesa [Rescher, 2017] e, alla fine, genera un paradosso. I paradossi sono stati usati molte volte in logica nel passato. Mentre, da un punto di vista, possono essere considerate storie divertenti da usare per insegnare, da un altro punto di vista sono strumenti potenti che mostrano i limiti di particolari aspetti formali di una situazione. Per esempio, uno dei più famosi paradossi in

Risoluzione: Se il bibliotecario fosse indipendente, ovvero si cercasse i libri da solo, verrebbe meno la premessa che lo vuole cercare i libri solo per le persone bisognose – quindi, se fosse indipendente allora sarebbe bisognoso. Se invece non fosse in grado di cercarsi i libri da solo, e quindi fosse bisognoso, allora dovrebbe farsi aiutare dal bibliotecario, che però è lui stesso – quindi, se fosse bisognoso allora sarebbe indipendente. Uno dei più importanti problemi studiati in informatica anni fa, che faceva parte dei 23 problemi aperti della matematica che David Hilbert propose nel 1900, è noto col nome di problema della terminazione. Questo problema riguardava il capire se fosse possibile sviluppare un algoritmo che fosse in grado di rispondere se un altro algoritmo, specificato come input, terminasse la sua esecuzione o no. L’algoritmo proposto in Figura 4 è un esempio di uno di quelli che termina, ma è possibile anche sviluppare un algoritmo che non termina mai, ad esempio come mostrato in quello definito in Figura 6. Avere un modo per scoprire sistematicamente se un algoritmo termina la sua esecuzione o no sarebbe di importanza cruciale, perché permetterebbe immediatamente di identificare quegli algoritmi che non lavorano in modo appropriato. Figura 6. Un diagramma di flusso che descrive un algoritmo che non termina mai, visto che la condizione “il numero è maggiore di 0” è sempre vera per costruzione. Uno degli scienziati che di più a lavorato alla risoluzione di questo quesito è stato Alan Mathison Turing (mostrato in Figura 7). Alan Turing è stato un informatico, nonché padre dell’informatica teorica e dell’intelligenza artificiale, anche se i suoi lavori hanno interessato diverse discipline tra cui la matematica (si vedano gli studi per decodificare la macchina Enigma), la logica (con l’introduzione della macchina di Turing [Turing, 1937]), la filosofia (si veda lo studio sulla relazione tra i calcolatori elettronici e il concetto di intelligenza [Turing, 1950]) e la biologia (si veda lo studio che identifica i processi spontanei di creazione di pattern in natura [Turing, 1952]).

Nel 1936, Turing sviluppò la sua macchina proprio per cercare di rispondere al problema della terminazione di Hilbert. La macchina proposta da Turing era prettamente teorica, nel senso che non l’aveva costruita fisicamente, anche se recentemente molte persone hanno provato a costruire prototipi fisici dell’idea di Turing, come quello mostrato in Figura 8. Figura 8. Un’implementazione fisica della macchina di Turing, con un nastro finito. Foto di GabrielF, sorgente: https://commons.wikimedia.org/wiki/File:Model_of_a_Turing_machine.jpg. La macchina, che è in grado di simulare l’esecuzione di qualunque algoritmo realmente implementabile, è composta da un nastro di memoria infinito composto da celle. Ogni cella può contenere un simbolo (o 0 o 1, dove 0 è usato come default per inizializzare le celle di tutto il nastro) che può essere letto e scritto dalla testina della macchina. Lo stato in cui la macchina si trova in un certo momento è altresì annotato. Le operazioni che può fare la macchina in un certo stato sono definite in una tabella (finita) di istruzioni, dove ogni istruzione dice cosa fare (scrivere un nuovo simbolo, muovere la testina a sinistra o a destra, spostarsi in un nuovo stato) in base allo stato in cui la macchina si trova e al simbolo presente nella cella sotto la testina. Infine, sono forniti anche uno stato iniziale e zero o più stati finali, in modo da sapere dove iniziare e finire il processo.

Figura 9. Il diagramma di flusso di un algoritmo che non termina se l’algoritmo specificato in input termina (verificato attraverso l’uso di dell’algoritmo ipotetico “termina?”), e restituisce 0 in caso contrario. Questo risultato ha avuto un effetto dirompente sulla percezione delle abilità computazionali che un computer può avere. In pratica, la macchina di Turing e le relative analisi effettuate su di essa hanno imposto dei limiti chiarissimi a quello che possiamo calcolare, e hanno permesso di dimostrare che determinati problemi computazionali interessanti, come quello della terminazione, non possono essere risolti da nessun approccio algoritmico.

Linguaggi di programmazione

1. Che cos’è un linguaggio?

Una linguaggio “naturale” è un linguaggio comune, come l’italiano, che può essere scritto o orale, e che si è evoluto in maniera naturale all’interno di una comunità. Da come li conosciamo, i linguaggi naturali hanno il vantaggio (e, contemporaneamente, lo svantaggio) di essere così tanto espressivi che specifiche istruzioni comunicate attraverso il loro uso possono sembrare ambigue. Consideriamo, per esempio, la frase “la vecchia porta la sbarra”. Questa frase significa che c’è una vecchia porta che blocca qualcuno o che c’è una signora anziana che trasporta una sbarra? Tuttavia, spesso, noi riusciamo a disambiguare il significato di una frase mediante l’uso di convenzioni sociali o analizzando il contesto in cui l’azione avviene, in modo da restringere il possibile significato di un pezzo di informazione. Mentre i linguaggi naturali non sono formali per definizione, molti studi in linguistica cercano di fornirne una formalizzazione mediante l’uso di strumenti matematici [Bernardi, 2002]. Una delle figure più importanti dietro alla formalizzazione del linguaggio naturale è Noam Chomsky (mostrato in Figura 1). Chomsky è uno dei più importanti accademici degli ultimi cento anni e uno dei padri della linguistica moderna, insieme a Ferdinand de Saussure, Lucien Tesnière, Luis Hjelmslev, Zellig Harris, Charles Fillmore, etc. Le sue ipotesi di ricerca principali sul linguaggio umano sono: (1) la sua struttura sintattica di base è rappresentabile mediante una teoria matematica e (2) tale struttura è determinata biologicamente in tutti gli umani – ovvero, è già presente in noi sin dalla nascita – e, come tale, è una caratteristica unica che si è evoluta nel tempo e che è condivisa soltanto dagli umani e non da altri animali. Questa sua visione del linguaggio umano è molto dibattuta, ma ha avuto il merito di fondare su basi rigorose la ricerca sulle relazioni tra competenza e produzione linguistica, sulle dinamiche e le interazioni tra capacità “innate” e sviluppo cognitivo e sociale delle abilità linguistiche. Ha anche permesso di chiarire in parte distinzioni come sintassi vs. semantica, lessico vs. discorso, tra rappresentazione logica e rappresentazioni mentali, etc. Tra la sua produzione accademica troviamo la classificazione delle grammatiche formali in una gerarchia di crescente potere espressivo, che rappresenta uno dei suoi più importanti contributi, considerando il suo impatto anche al di fuori della

linguistica tradizionale – per esempio nel dominio dell’informatica teorica e nei linguaggi di programmazione, come vedremo in seguito. Una grammatica formale è uno strumento matematico usato per definire la sintassi di un linguaggio (sia lingue naturali come l’italiano, sia linguaggi artificiali) attraverso l’uso di un insieme finito di regole di produzione, che permettono di costruire una qualunque frase valida in quello specifico linguaggio. Una grammatica formale è composta da un insieme di regole di produzione di forma premessa ::= espressione (in notazione Backus–Naur, or BNF), dove la premessa e l’espressione possono contenere uno o più simboli delle seguenti tipologie:  simbolo terminale (specificato tra virgolette in BNF), che identifica tutti i simboli elementari del linguaggio in considerazione (come nomi, verbi, etc.);  simbolo non terminale (specificato tra parentesi angolari in BNF), che identifica tutti i simboli di una grammatica formale che possono essere sostituiti da una combinazione di simboli terminali e non terminali. In linea di principio, l’applicazione di una regola di produzione riguarda la sostituzione dei simboli nella premessa con quelli specificati nell’espressione finché non si raggiunge una sequenza che include soltanto simboli terminali. Per esempio, le regole di produzione ::= "Io" , ::= "scrivo" e ::= "leggo" permette la creazione di tutte le frasi di due parole che hanno il pronome di prima persona singolare accompagnato da uno dei verbi possibili (ad esempio “Io scrivo”). In più, ogni grammatica formale deve specificare un simbolo di inizio, che deve essere non terminale. La gerarchia proposta da Chomsky mette a disposizione un modo per descrivere formalmente le relazioni che possono esistere tra diverse grammatiche in termini delle loro possibili strutture sintattiche che sono in grado di generare. In pratica, queste tipologie sono caratterizzate dal tipo di simboli che possono essere usati nella premessa e nell’espressione delle regole di produzione. Queste tipologie di grammatica sono elencate di seguito, dalla meno espressiva alla più espressiva [vengono usate le lettere dell’alfabeto greco per indicare una qualsivoglia possibile combinazione di simboli terminali e non terminali, incluso il simbolo terminale vuoto, solitamente rappresentato con ε]:  grammatiche regolari – forma delle regole di produzione: o <non-terminale> ::= "terminale" o <non-terminale> ::= "terminale" <non-terminale> o Esempio: ::= "Io" ::= "scrivo" ::= "leggo"  grammatiche libere dal contesto – forma delle regole di produzione: o <non-terminale> ::= γ