



































Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Dispensa di informatica di base per Lettere Moderne
Tipologia: Dispense
1 / 43
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!




































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 e si riferisce a qualcuno che “esegue calcoli matematici”.
Il termine “computer”, è considerato come 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, o 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).
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 con l’idea 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 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.
Nel 1837, Babbage iniziò a progettare una nuova macchina, la Macchina Analitica che 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.
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) sviluppata da Alan Turing, che è stata il principale strumento che ha permesso a un gruppo di persone, rinchiuse nella base militare segreta britannica, di decifrare (grazie anche al lavoro pregresso fatto da crittologi) 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 storico.
Il primo computer interamente digitale, come pensato da Babbage con la sua Macchina Analitica, è stato sviluppato negli Stati Uniti d’America nel 1946: l’ Electronic Numerical Integrator and Computer (ENIAC), , che era completamente programmabile attraverso l’uso di cavi e interruttori.
Un computer può essere sia una macchina sia un essere umano.
Scrivere un programma significa 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 proviamo a ricercare possibili soluzioni per il problema in questione confrontandolo con possibili situazioni che si sono già presentate in passato – e che, 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.
Considerando le situazioni e contesti appena riportati, possiamo definire il pensiero computazionale come un approccio per risolvere problemi, sviluppare sistemi e capire il comportamento umano che riprende i concetti fondamentali della computazione [Wing, 2008]
Jeannette Wing fornisce una definizione aggiuntiva, per chiarire ancor meglio cosa si intenda per pensiero computazionale [Wing, 2008]: “Il pensiero computazionale è un tipo di pensiero analitico. Condivide, con il pensiero matematico, il modo in cui possiamo approcciare un problema per trovarne la soluzione. Condivide, con il pensiero ingegneristico, il modo in cui possiamo affrontare la progettazione e la valutazione di grandi sistemi complessi che operano all’interno dei limiti del mondo reale. Infine, condivide, con il pensiero scientifico, il modo in cui possiamo trattare la comprensione della calcolabilità, l’intelligenza, la mente e il comportamento umano.”
La nozione principale dietro al pensiero computazionale è l’astrazione, ovvero l’abilità di esercitare pensiero astratto e di esibire abilità di astrazione – che comporta il processo di rimozione dei dettagli trascurabili di una situazione in modo da semplificarla, per così focalizzare l’attenzione sulle sue caratteristiche principali [Kremer, 2007]. L’abilità di astrarre situazioni e nozioni è cruciale per automatizzare l’esecuzione di determinate operazioni attraverso l’uso di un computer che è responsabile dell’interpretazione di queste astrazioni.
Uno degli obiettivi del pensiero computazionale è quello di dare nuovamente forma alle astrazioni che abbiamo già immagazzinato in passato come conseguenza della nostra esperienza. Quindi, per essere nuovamente e interamente coscienti di queste astrazioni, dobbiamo ridefinirle usando un linguaggio appropriato per renderle comprensibili a un computer.
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.
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.
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 sola lista.
L’organizzazione a scatole appena presentata descrive una precisa gerarchia tra loro, dove la più grande (ovvero book) ne contiene di più piccole (ovvero i vari chapter), queste a loro volta ne contengono di più piccole ancora (i paragraph), e così via.
Un albero è una struttura dati composta da un insieme di nodi collegati tra loro da una relazione gerarchica genitore-figlio. 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.
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:
L’origine della struttura a grafo deriva da una piccolo gioco, conosciuto ai tempi come un vero e proprio problema matematico, che riguarda sette ponti di una specifica città, Königsberg. Il problema può essere enunciato come segue: è possibile fare una passeggiata in città attraversando ogni ponte una ed una sola volta? Per questa dimostrazione, Eulero ha descritto, in modo astratto, le quattro terre che formavano Königsberg come nodi di una rete collegati da archi, dove ogni arco tra due nodi rappresentava un ponte tra due lembi di terra.
I grafi sono una delle principali strutture dati in informatica e, in generale, del pensiero computazionale. Sono usati per descrivere, in termini astratti, molte situazioni del mondo reale come tragitti tra città, relazioni tra persone nei social network, l’organizzazione dei collegamenti ipertestuali tra pagine Web [Albert and Barabasi, 2002] e le relazioni concettuali nelle basi di dati o nei knowledge graph usati per esempio da Google, Amazon e Facebook per i loro servizi.
Questa struttura dati è interamente derivata dall’omonimo strumento matematico inventato da Eulero, e possono essere distinti in due macro-categorie: grafi non orientati, come quello usato da Eulero per risolvere il problema dei sette ponti di Königsberg, dove un arco può essere attraversato in una direzione o nell’altra a piacere, ed i grafi orientati, dove ogni arco specifica esplicitamente la direzione di percorrenza.
Ada Lovelace era la figlia del poeta Lord Byron. Matematica di formazione, è diventata famosa per il suo lavoro sulla Macchina Analitica di Babbage. 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]:
“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.
Gli algoritmi accompagnano sistematicamente le nostre attività della vita quotidiana.
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).
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
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.
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.
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 matematica è il paradosso di Russell, scoperto da Bertrand Russel nel 1901. È stata una delle più grandi scoperte dell’inizio del ventesimo secolo, poiché ha provato che l’allora teoria degli insiemi proposta da Georg Cantor, e usata come base fondazionale per tutto il lavoro che Gottlob Frege stava facendo per definire le leggi di base dell’aritmetica, portava ad una contraddizione. Di conseguenza, il paradosso di Russell invalidava l’intera teoria degli insiemi e il lavoro fatto da Frege stesso – che era in stampa quando Russell gli comunicò la sua scoperta. Una variazione del paradosso può essere formulata come segue.
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.
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.
La macchina è stata usata da Turing per mostrare una soluzione per il problema della terminazione. In questo capitolo presentiamo un’approssimazione alla soluzione che Turing ha fornito, basata interamente sulla reductio ad absurdum, già usata per risolvere il paradosso del bibliotecario, e sui diagrammi di flusso come un’astrazione grafica di una specifica macchina di Turing.
Supponiamo sia possibile sviluppare l’algoritmo “termina?”, che prende in input un certo algoritmo e restituisce “vero” nel caso in cui l’algoritmo specificato come input termina, mentre restituisce “falso” in caso contrario. Ovviamente, questo è soltanto un algoritmo ipotetico: stiamo supponendo che possiamo svilupparlo in qualche modo, senza mostrare come farlo davvero.
Ora, usiamo l’algoritmo “termina?” per sviluppare un nuovo algoritmo, introdotto nel diagramma di flusso in Figura 9. Questo nuovo algoritmo prende in input un algoritmo e restituisce 0 se l’algoritmo in input termina, mentre non termina in caso contrario. Notate che siamo in grado di implementare davvero ogni passo di questo nuovo algoritmo, siccome scoprire se l’algoritmo in input termina è restituito dal nostro algoritmo (ipotetico) “termina?”, mentre la non terminazione è un processo chiaramente implementabile, visto che ne abbiamo introdotto un esempio in Figura 6.
Ora la domanda è: cosa succede se cerchiamo di eseguire l’algoritmo descritto in Figura 9 usando se stesso come input? Abbiamo due situazioni possibili:
Quindi, qualunque sia il comportamento del nostro algoritmo in Figura 9, la sua esecuzione
passando se stesso come input genera sempre una contraddizione. L’unica spiegazione possibile, quindi, è che l’algoritmo ipotetico “termina?” che usiamo per decidere se un
algoritmo termina o meno non può essere sviluppato. Di conseguenza, la risposta al problema della terminazione è che l’algoritmo che verifica se un altro termina non può esistere.
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
C’è un particolare aspetto di ogni computer (sia esso umano o macchina) che non è stato ancora affrontato direttamente, ovvero: quale meccanismo possiamo usare per chiedere a un computer di eseguire una particolare attività? La modalità per affrontare questo problema è estremamente connessa con il particolare canale comunicativo che vogliamo adottare. Se consideriamo un computer umano, possiamo usare un linguaggio naturale (ad esempio l’italiano) o i diagrammi di flusso per istruirlo sui passi algoritmici che deve compiere. Invece, per comunicare efficacemente con un computer elettronico si usano i linguaggi di programmazione.
Un linguaggio di programmazione è un linguaggio formale che obbliga l’uso di specifiche regole sintattiche sviluppate in modo tale da evitare possibili istruzioni ambigue – solitamente restringendo l’espressività del linguaggio – cosicché tutte le “frasi” componibili possano trasmettere un solo possibile significato. I linguaggi di programmazione sono solitamente
basati su grammatiche libere dal contesto, in conformità con la classificazione Chomskiana fornita nella sezione precedente – e possono distinguersi per un basso o elevato livello di astrazione dal linguaggio propriamente in uso da un elaboratore elettronico per eseguire le operazioni. In particolare, possiamo raggruppare i linguaggi di programmazione in tre macro insiemi:
@@: cmp edx, 2 ja @f mov eax, 1 ret
@@: push ebx mov ebx, 1 mov ecx, 1
@@: lea eax, [ebx+ecx] cmp edx, 3 jbe @f mov ebx, ecx mov ecx, eax dec edx jmp @b
@@: pop ebx ret
gioco, si imparano implicitamente e, da un certo punto di vista, in modo completamente inconsapevole, dei concetti chiave propri alla programmazione e al pensiero computazionale.
La sequenza di istruzioni specificate per risolvere il problema computazionale proposto di fatto
implementano uno specifico algoritmo. Utilizzando i diagrammi di flusso, introdotti nel capitolo precedente, è possibile specificare lo stesso insieme di istruzioni usando un linguaggio più
formale.
Mano a mano che si va avanti nel gioco, il numero di azioni che si devono far compiere al coniglio diventa più elevato. In modo da semplificare la specificazione di queste lunghe sequenze di azioni, il gioco propone
costrutti addizionali mano a mano che si va avanti nei vari livelli di difficoltà. Per esempio come eseguire una sequenza di azioni, per risolvere il problema computazionale, mediante l’utilizzo
di un nuovo oggetto grafico (o costrutto) che permette di raggruppare sequenze ripetitive di azioni e di eseguirle un numero definito di volte.
Anche questo algoritmo prettamente grafico può essere rappresentato mediante l’utilizzo di un diagramma di flusso, come mostrato in Figura 8. In questo caso, la ripetizione è realizzata attraverso l’utilizzo di un oggetto grafico decisionale che controlla se la sequenza di azioni “fai due passi avanti” e “gira a destra” è stata eseguita il numero necessario di volte (4) per risolvere il problema computazionale, e così concludere l’algoritmo.
Esistono anche altri linguaggi di programmazione visuale che sono più general-purpose, ovvero
che permettono di sviluppare algoritmi per risolvere problemi computazionali di vario genere. Uno di questi è Blocky, creato da Google. Questo linguaggio mette a disposizione diversi costrutti propri dei linguaggi di programmazione tradizionali, ma li propone sotto forma di
blocchetti che si possono incastrare uno sull’altro o uno dentro l’altro per definire le sequenze di operazioni di un algoritmo.
Figura 9. L’implementazione in Blocky della sequenza di azioni mostrate in Figura 4. In questo caso, le azioni sono organizzate all’interno di una lista, una particolare struttura dati introdotta nel primo capitolo del corso.
In Figura 9 è introdotto un algoritmo in Blocky che costruisce la sequenza di azioni inserendole dentro una lista nell’ordine in cui vanno fatte eseguire al coniglio. In questo caso, viene
inizializzata una variabile istruzioni con una lista vuota che, nelle operazioni successive, viene riempita con le operazioni che devono essere eseguite dal coniglio. In modo da mantenere il
giusto ordine delle azioni della sequenza, ogni nuova istruzione viene inserita alla fine della lista.
E’ possibile utilizzare specifici costrutti di Blocky per permettere ripetizioni sistematiche di un gruppo di istruzioni. Per esempio, in Figura 10 viene mostrato l’utilizzo del costrutto repeat
while
Figura 10. L’implementazione in Blocky della sequenza di azioni mostrate in Figura 7. In questo caso, le azioni vengono inserite all’interno della lista mediante un costrutto che permette la ripetizione di tre specifiche operazioni fintanto che la condizione iterazione < 4 è vera.
Oltre alla possibilità di creare graficamente un algoritmo, Blocky mette anche a disposizione una sorta di traduttore che permette di descrivere i vari passi dell’algoritmo implementato in
uno tra cinque diversi linguaggi di programmazione. Per esempio, il Listato 1 mostra la traduzione in Python delle istruzioni presentate in Figura 10.
istruzioni = list() iterazione = 0
while iterazione < 4: iterazione = iterazione + 1 istruzioni.append("avanti") istruzioni.append("avanti") istruzioni.append("gira a destra")
Listato 1. L’implementazione in Python dell’algoritmo descritto in Blocky mostrato in Figura 10.
Python è un linguaggio di programmazione ad alto livello general-purpose, ed è correntemente uno dei linguaggi più usati per il Web e per attività di analisi automatiche di dati e del linguaggio naturale. Tra i vari vantaggi, Python è uno dei linguaggi più semplici con cui iniziare a studiare come programmare e creare applicazioni.
In questo capitolo e nei successivi affronteremo tutte le tecnologie alla base di Internet e del World Wide Web.
Cosa succede quando si clicca su un collegamento ipertestuale (o link) di una pagina Web?
completamente meccanico, flessibile, e veloce. In particolare, il memex era composto da una scrivania con due schermi al centro per facilitare la lettura dei documenti memorizzati,
accompagnati da una tastiera, un insieme di bottoni e leve (a destra), e, sulla sinistra, una plancia su cui posizionare qualunque documento così da essere fotografato e memorizzato nei
microfilm utilizzati dalla macchina.
Tutte le informazioni memorizzate (ad esempio, un libro) potevano, ovviamente, essere recuperate mediante il classico meccanismo a indici descritto sopra, e consultate attraverso l’uso delle leve messe a disposizione dalla macchina, che permettevano di sfogliare sequenzialmente documenti. Inoltre, la macchina metteva a disposizione anche un meccanismo per annotare i documenti visualizzati, così da aggiungere note a margine, commenti, e quant’altro.
In aggiunta a questo, il più grande vantaggio che il memex avrebbe introdotto era la possibilità di creare percorsi tra le informazioni, ovvero dei collegamenti associativi tra le varie porzioni di informazioni memorizzate nel memex. In particolare, un utente avrebbe potuto creare uno specifico percorso associativo e memorizzarlo nella macchina con un nome, così da poterlo richiamare ed estendere in futuro. In questo caso, il memex avrebbe permesso non solo di spostarsi sequenzialmente sul documento visualizzato, ma anche trasversalmente su diversi documenti seguendo tutti i percorsi associativi che un utente avrebbe potuto creare. Infine, avrebbe anche potuto salvare il percorso in uno specifico microfilm così da poterlo condividere con un collega, che avrebbe potuto visualizzarlo nel suo memex.
Le idee contenute nell’articolo di Vannevar Bush erano visionarie e d’avanguardia per l’epoca, e alcune di queste rappresentano ancora dei possibili desiderata dello sviluppo degli ipertesti e, più in generale, del Web. Molti ingegneri e inventori sono stati ispirati da quest’articolo nel corso degli anni successivi. Tra questi è bene ricordare Theodor (Ted) Holm Nelson , colui che ha coniato la parola ipertesto.
Nel 1960, Ted Nelson aveva iniziato a lavorare ad un nuovo progetto, chiamato Xanadu – un esplicito riferimento alla città capitale del regno di Kublai Khan. L’obiettivo del progetto era
quello di creare una rete di computer che potesse essere depositaria dell’intera conoscenza umana e che potesse essere estesa mediante dei collegamenti ipertestuali bidirezionali che
permettessero di collegare tra loro idee contenute in diversi documenti, riprendendo il concetto di percorsi associativi postulato da Vannevar Bush [Nelson, 1965]. Inoltre, in questo lavoro,
Nelson ha introdotto una serie di concetti chiave – ad esempio: trasclusione e intertwingularity, che non ha corrispettivi in italiano, ma richiama esplicitamente il concetto di processo
associativo della conoscenza tipico degli esseri umani enunciato da Vannevar Bush – che sono stati fondamenti pregnanti di tutti gli ipertesti sviluppati successivamente.
Nello stesso periodo in cui Nelson divulgava le sue idee e il suo progetto, un altro scienziato, Douglas Engelbart , aveva iniziato l’implementazione di un sistema rivoluzionario per l’epoca,
che di fatto permetteva per la prima volta la creazione di ipertesti su un computer. Questo sistema, chiamato oN-Line System (NLS) ebbe un impatto dirompente sullo stato corrente e
futuro delle tecnologie informatiche che erano a disposizione all’epoca. Basti pensare le innovazioni software (le applicazioni e i programmi a disposizione di un computer) e hardware
(ovvero la parte fisica e tangibile di un computer, come il processore, il disco fisso, la memoria RAM, etc.) che lo sviluppo dell’NLS aveva portato: il concetto di finestre come meccanismo
visuale per l’organizzazione di contenuti e applicazioni di un sistema operativo, l’ipertesto, un sistema di videoconferenza, il mouse , un sistema di videoscrittura (o word processor in inglese, tipo Microsoft Word), un meccanismo (chiamato dynamic linking) per richiedere solo
quando necessario opportune librerie e applicazioni a disposizione sul computer, meccanismi per il controllo delle versioni dei documenti, e un editor collaborativo in tempo reale (tipo
Google Docs). Tutte queste cose non esistevano prima di NLS, e sono da lì in avanti state il fondamento per lo sviluppo dei moderni computer e sistemi operativi.
L’intero sistema, implementato realmente da Engelbart e soci, era stato per la prima volta mostrato in una famosa sessione dimostrativa nella Fall Joint Computer Conference del 1968,
organizzata dalla Association for Computing Machinery e dall’Institute of Electrical and Electronics Engineers (le due principali associazioni internazionali accademiche di informatica),
suscitando commenti entusiastici da parte di tutta la comunità, tanto da aver poi etichettato quella sessione come la madre di tutti i demo (The Mother Of All Demos, in inglese).
Sono dovuti passare diversi anni dalla madre di tutti i demo, poco più di trenta, prima di poter assistere ad un’implementazione su scala mondiale di un ipertesto, il World Wide Web o, più semplicemente, il Web [Berners-Lee et al., 1994]. L’idea venne ad un giovane scienziato del CERN di Ginevra nel 1989, Tim Berners-Lee. Il progetto, inizialmente respinto da parte dei suoi superiori con il famoso commento “vago ma eccitante venne poi accettato come progetto collaterale per testare le potenzialità di un nuovo computer (il NeXT, realizzato dall’ omonima azienda di proprietà di Steve Jobs negli anni di separazione da Apple) che era stato appena comprato dal CERN. Nell’idea originale di Berners-Lee, il Web avrebbe dovuto essere un ipertesto che potesse descrivere una varietà di risorse eterogenee, tra cui documenti, persone, gruppi di persone, organizzazioni, concetti astratti, e quant’altro. Inoltre, sarebbe dovuto anche essere possibile collegare tra loro queste risorse mediante l’uso di collegamenti ipertestuali etichettati, in modo da poter specificare la semantica del collegamento come parte esplicita del link. Questa visione è riassunta nel diagramma in Figura 8, estratto da una copia del documento originale che Berners-Lee aveva scritto per convincere il CERN a finanziare il progetto. Seppur ispirato alle idee di Ted Nelson, l’infrastruttura ideata da Berners-Lee proponeva alcune semplificazioni, prima tra tutte il fatto che tutti i collegamenti ipertestuali del Web dovessero essere percorribili in un’unica direzione, contrariamente all’organizzazione postulata originariamente da Ted Nelson, ove tutti i collegamenti ipertestuali erano pensati per essere percorribili in una direzione o nell’altra indistintamente.
Il primo prototipo del Web realizzato da Tim Berners-Lee, tuttavia, aveva ulteriormente semplificato lo scenario, riducendo ad una soltanto, il documento o pagina Web, la tipologia di
risorse descrivibili e permettendo una sola tipologia di collegamento ipertestuale, ovvero il semplice riferimento (il documento A fa riferimento al documento B). In modo da realizzare
questa visione, Berners-Lee sviluppò una serie di tecnologie, basate su altre già esistenti e messe a disposizione negli anni precedenti, che permettessero agli utenti la creazione di documenti ipertestuali (l’Hypertext Markup Language, o HTML), di mettere a disposizione i
suddetti documenti ipertestuali sul Web (attraverso la creazione di server web), di poterli richiedere mediante l’utilizzo di un opportuno identificativo (chiamato Uniform Resource
Locator, o URL) e di uno specifico protocollo di comunicazione (l’Hypertext Transfer Protocol, o HTTP), e infine di visualizzarli su un computer (attraverso l’uso di un browser). Tutte queste
tecnologie sono tutt’ora in uso e rappresentano le fondamenta del Web, seppur questo si sia evoluto molto negli ultimi anni e si sia sempre più conformato all’idea iniziale che aveva Tim