Digi Tales

Pensiero computazionale sì o no? Boh, dipende dal linguaggio.

Gen
27

Una delle motivazioni dietro la spinta all’introduzione della programmazione dei computer in giovane età (=coding) è quella per cui questa pratica insegna il pensiero computazionale, che è un modo di affrontare i problemi in maniera scientifica. Anche se Jeannette Wing si è affannata a dire che non si tratta di insegnare ai bambini a pensare come i computer, e Seymour Papert a predicare che bisogna insegnare ai computer come se fossero bambini, questo tema resta nell’aria. Computazionale significa “razionale, finito, corretto, misurabile” eccetera. In pratica, imparare il pensiero computazionale significa, per ogni problema, saper immaginare un algoritmo che lo risolva. Questo algoritmo deve essere eseguibile praticamente da un computer: non deve contenere errori, non deve basarsi su termini ambigui, non deve richiedere un tempo infinito o una memoria infinita per arrivare alla soluzione.

Però gli algoritmi si pensano (e poi si scrivono) diversamente a seconda del linguaggio, e a seconda del tipo di linguaggio che si ha in mente. Usare un linguaggio semplice quando si è ai primi passi è sicuramente una buona idea; ma cosa significa “semplice”? Può significare che costringe ad usare dei concetti e delle operazioni di base, vicine a quelle che sa fare il computer; oppure che spinge a nascondere i dettagli e a concentrarsi “semplicemente” sulla struttura dei dati e sugli obiettivi.

Insegnare ai bambini una forma di pensiero computazionale che li abitua a pensare nei termini del primo significato di “semplicità” rischia di creare più problemi – in futuro – di quelli che risolva adesso.

Voglio provare a spiegare questa differenza con un piccolo esempio.

Partiamo da Snap!, un ambiente di programmazione che pochi conoscono, che assomiglia molto a Scratch (ne è derivato) ma ha delle differenze importanti. Intanto è nato all’Università di Berkeley, dall’altra parte degli USA rispetto a Stanford, dove è nato Scratch. Poi ha un target più ampio, nel senso che può essere usato da bambini ma anche da ragazzi più grandi, perché è basato su un modello di linguaggio più potente di quello che sta sotto Scratch. Le differenze tra Snap! e Scratch sono poco visibili ad un primo approccio (si possono disegnare girandole o creare scenette animate con entrambi), ma sono molto profonde. Ne elenco solo tre fondamentali:

  1. A differenza di Scratch, in Snap! si possono creare funzioni, cioè procedure che alla fine restituiscono un valore, e questo è molto comodo perché permette di costruire delle catene di funzioni che si applicano ai risultati di altre funzioni.
  2. In secondo luogo, in Snap! ci sono delle funzioni per creare e gestire vere liste (di numeri, di lettere, di parole, ma anche di sprite, o di qualsiasi oggetto).
  3. Inoltre le funzioni possono essere usate come oggetti da altre funzioni. In altre parole: una funzione si può applicare non solo a oggetti semplici (come numeri, lettere) ma anche ad altre funzioni. In aritmetica conosciamo questo tipo di cose: la moltiplicazione è l’applicazione ripetuta della funzione somma.

Il terzo punto è particolarmente utile quando si vogliono trattare delle serie di dati. Se vogliamo trasformare una lista di numeri, o di lettere, o di parole, in qualche altra cosa dobbiamo applicare una trasformazione ad ognuno degli elementi della lista per ottenere una nuova lista; avere un linguaggio che permetta di fare direttamente questo tipo di operazioni permette di concentrarsi sul problema, e non sul meccanismo delle soluzione.

Mettiamo, per esempio, di voler generare la lista dei quadrati dei primi 10 numeri interi. Partendo da una lista di numeri interi, vogliamo moltiplicare ogni numero per se stesso. Cioè da 1,2,3,4,5… vogliamo ottenere 1,4,9,16,25 …

Questo in Scratch si può fare così:

  • crea una lista di numeri interi e dagli il nome “numeri”
  • crea una seconda lista e dagli un nome “quadrati”
  • crea un contatore e dagli il nome “i”, e valore 0
  • ripeti tante volte quanto è la lunghezza della lista “numeri”:
    • aumenta “i” di 1
    • moltiplica l’elemento i della lista “numeri” per se stesso
    • aggiungi il risultato alla lista “quadrati”

Saltando la parte di creazione delle variabili, ecco i blocchetti:

E’ semplice, sì, ma è anche un modo di pensare molto meccanico, che segue esattamente quello che succede “sotto il cofano”. Siamo noi che ci adeguiamo al modo di lavorare del computer, e non viceversa. Arrivare a questa formulazione non è banale: bisogna imparare a pensare in un certo modo. Bisogna imparare il concetto di contatore, di indice di una lista, quello di ciclo, di test di terminazione, eccetera. Sono gli elementi di base della programmazione classica.

Proprio questo è uno dei limiti dell’approccio “computazionale” di cui parlavamo sopra: si impara a pensare come un computer, o meglio secondo gli schemi che il linguaggio di programmazione che usiamo (in questo caso Scratch) mette a disposizione.

Da un lato questo apprendimento ha degli aspetti positivi: ogni volta che ci poniamo un limite impariamo nuovi modi di risolvere problemi. Dall’altro, restare dentro quei limiti non ci permette di andare molto lontano. Potrebbe esserci (non è mai stato studiato a fondo) una forma di imprinting per cui il primo modello di programmazione imparato resta quello fondamentale, un po’ come la lingua madre. Se i bambini imparano a usare i cicli per scorrere le liste, poi continueranno a farsi venire in mente questa modalità anche in futuro.

Ma usare i cicli è solo uno dei possibili modi di risolvere il problema dei quadrati. Ce ne sono altri, che dipendono dal linguaggio che si usa.

In Snap! si possono disporre i blocchetti esattamente nello stesso modo che in Scratch:

Ma si può fare anche qualcosa di diverso, più semplice e soprattutto più vicino alla struttura del problema.

Per risolvere il problema dei quadrati dei numeri interi non siamo costretti a pensare in termini di contatori e di indici, non dobbiamo per forza preoccuparci di sapere quando fermare il processo, dove mettere i risultati. Queste sono cose che riguardano la meccanica dell’algoritmo, non il problema in sé.

Qual era la cosa che volevamo fare, l’idea iniziale? Applicare la funzione “moltiplica per se stesso” a tutti gli elementi di una lista. Bene, allora cominciamo dalla lista

Questo blocchetto è una funzione che restituisce una lista costruita con gli oggetti che mettiamo negli spazi bianchi, in questo caso i primi cinque numeri interi. Non abbiamo bisogno di creare una variabile perché la funzione restituisce sempre quello che ci serve. Ora possiamo disporre i blocchetti in modo da rispettare esattamente l’idea originale di applicare la moltiplicazione alla lista:

Finito. Non c’è altro da fare. Questo blocchetto (“applica”) fa da solo tutto il lavoro: prende il primo elemento della lista, lo inserisce nei due buchi del blocchetto verde, esegue la moltiplicazione e mette da parte il risultato, poi passa al successivo. Alla fine restituisce una nuova lista con i risultati.

La presenza del blocchetto “applica” è una delle caratteristiche di Snap! (e dei linguaggi funzionali a cui si ispira) a cui facevamo riferimento prima. E’ una funzione che lavora su altre funzioni. Usando questo approccio, non ci servono variabili, contatori, cicli. Per certi versi, è molto più intuitivo e vicino al problema questo approccio di quello precedente, in cui dovevamo introdurre una serie di concetti di supporto.


In Snap! c’è un’altra maniera di ottenere questo risultato, ancora più semplice di quella che abbiamo visto adesso. Torniamo all’idea di partenza: “moltiplicare ogni numero di una lista per se stesso”. Ma invece di moltiplicare ogni numero per se stesso, possiamo partire da due liste identiche, e moltiplicare ogni elemento della prima lista per il corrispondente elemento della seconda.

Proviamo a fare esattamente questo:

Il risultato è lo stesso. In sostanza, l’operazione “moltiplicazione” normalmente si applica a dei numeri; ma in Snap! è stata estesa per applicarsi anche a delle liste. Naturalmente potremmo pensare di utilizzarla anche per moltiplicare due liste diverse, o per moltiplicare una lista per un numero singolo. Forse funziona anche con altre operazioni? Proviamo con l’operazione “elevamento a potenza”:

Funziona. Ed è abbastanza chiaro che è molto più semplice di tutto l’ambaradam che avevamo dovuto mettere in campo prima. Questa modalità ci libera di tutta la parte meccanica e ci spinge invece verso una maniera diversa di concepire il problema che volevamo risolvere, che a sua volta ci apre la strada verso altre possibilità.

In conclusione: attenzione al linguaggio che si sceglie, e non confondiamo la razionalità con la meccanicità.

Tecniche e tecnologie per la fantasia

Dic
11

Fantasia e tecnica non vanno d’accordo, si direbbe. Meno ancora fantasia e tecnologia: se c’è una, scompare l’altra. Quando entra in campo la tecnologia, il libero gioco dell’immaginazione dove va a finire? Però, però…

La tecnica del sasso nello stagno è descritta da Gianni Rodari nel secondo capitolo della Grammatica della Fantasia, come parte dello strumentario che serve a chi vuole inventare storie per i bambini, o con i bambini.
E’ una tecnica fondata sulle relazioni che uniscono parole nella mente, ma anche sul potere del caso nel sorprenderci e generare l’inizio di una storia.

Si pensa una parola… o meglio: la si chiede a qualcuno che passa di là, oppure la si pesca da un dizionario, o da un libro. Perché un libro? Perché è ancora più casuale, e dunque più sorprendente. Apri il libro a pagina 27, prendi la terza parola della quinta riga.
Poi si parte da quella parola per esplorare una serie di associazioni che servono a far emergere altre parole.
Per esempio, partendo da stasera possiamo andare a cercare:

le parole che cominciano allo stesso modo: stabilire, staccare, stagione, storia, studiare, stupido, …

le parole che finiscono allo stesso modo , cioè che sono più o meno in rima con stasera: camera, eccetera, lettera, maniera, opera

le parole simili, che si usano nello stesso contesto: stamattina, stanotte, prima, dopo, più tardi

E già nasce una storia:

Stasera, in camera, apro una lettera: sempre la stessa storia, la solita maniera, come un’opera eccetera eccetera – stupido! –

fino ad usare la lettere che compongono la parola per fare un acrostico:

  • Strada
  • Treno
  • Anima
  • Sangue
  • Egli
  • Ritornare
  • Appunto

Tutte queste parole nuove possono essere usate per creare una storia o una filastrocca.
Posiamo programmare un computer per fare (almeno una parte di) queste operazioni?
Sì, ed è quello che ho fatto qui con iKojo, la versione online di Kojo:
http://ikojo.in/sf/vylXgjC/10
Cliccate su RUN, prendete nota delle parole che vengono fuori (compreso l’acrostico) e poi iniziate a scrivere.
A me, partendo dall’acrostico di sopra – generato appunto dal programma – viene in mente una storia titanica (nel senso del film):

Lui è partito, col treno, chissà quanta strada ha fatto, ma ama lei fin nel profondo dell’anima. Lo ha giurato col sangue. Le aveva detto che sarebbe tornato, una sera di queste, e appunto, stasera…

Fa piangere già così, immaginate se poi lui stasera non dovesse arrivare …

Un altro risultato del Sasso nello Stagno

Il binomio fantastico è un’altra tecnica notissima, sempre dai primi capitoli della Grammatica.
Qui si prendono due parole a caso, possibilmente dalla mente di due persone diverse, o ancora una volta da un dizionario, da un giornale (dal web?).
Gli esempi di Rodari riguardano due sostantivi, ma non si vede perché non si potrebbero usare aggettivi o verbi; anzi, ci sono splendidi esempi di applicazione di questa variante nei racconti che mettono in scena i gemelli terribili, Marco e Mirko: “il leone bela”, “il lupo è dolce”, “il cielo è maturo”.
I due sostantivi si mettono insieme usando delle preposizioni: con, di, su, in (ma io aggiungerei: sotto, sopra, con, senza…).
Cosa fanno? si incontrano, si scontrano, vanno d’amore e d’accordo?
Per esempio: sasso, stagno (guarda un po’ che fantasia…)

  • il sasso nello stagno
  • il sasso di stagno
  • il sasso sotto lo stagno
  • lo stagno del sasso
    eccetera.

Ogni frase può essere l’inizio di una storia.
A me il sasso di stagno piace molto, mi fa pensare ad una palla di carta stagnola, di quelle della cioccolata, che usavo per giocare a calcio in corridoio da piccolo. E’ un sasso gentile e bellissimo, luccica come una pepita.

Una volta un minatore che lavorava a Canale Serci, sul monte Mannu, volle fare una sorpresa a suo figlio di tre anni e gli portò un sasso di stagno. Ma non era un sasso, era …

Se preferite, partiamo con una filastrocca. Il sasso di stagno richiama subito un ragno dispettoso, forse geloso, ma di chi? facile: di un cigno

Quel sasso di stagno
tirato da un ragno
geloso di un cigno
più bianco del regno…

Di nuovo vi chiedo: potremmo programmare un computer per fare (almeno una parte di) queste operazioni?
Sì, ed è quello che ho fatto qui:
http://ikojo.in/sf/EnNhE3O/12
Cliccate su RUN e state a guardare.


Sull’importanza del caso, sulla sua magia che ci costringe a renderci conto di quello che siamo (esseri che non possono impedirsi di dare senso a qualsiasi configurazione casuale, che vedono strutture chiuse ovunque) e sulla sua importanza per la didattica ho già scritto qui.

Se date un’occhiata al codice sorgente (a sinistra) vedete che a dispetto della semplicità apparente c’è invece parecchio lavoro sotterraneo per riuscire a mettere un articolo davanti ad un nome, o per costruire la preposizione articolata corretta. Non perché sia difficile la programmazione: perché è difficile la grammatica italiana, che deve dar conto di quasi mille anni di storia, di prestiti, di varianti locali, di sovrapposizioni. Anche questo è un esercizio di coding interessante, che richiede riflessione su come funzionano i meccanismi della lingua per poterli trasformare in algoritmi. Non è sempre necessario ricostruire tutto da capo, si può partire da pezzettini già pronti – è quello che ho fatto io e che fa chiunque programmi un computer.
L’idea più generale di accoppiare a caso sostantivi, proprietà, azioni, luoghi sta all’origine delle creazione di Limericks casuali a partire da una struttura (sintattica, ma anche narrativa): qualcuno, in qualche posto, fa qualcosa, e allora succede qualche altra cosa. Come finisce? E’ lo schema di tanti giochi, e anche delle fiabe a ricalco, un’altra tecnica descritta nella Grammatica della Fantasia nel capitolo 21 (ma ripresa anche in molti di quelli seguenti).
Entrambe queste possibili “applicazioni rodariane” del coding le ho descritte in “Lingua, coding e creatività”, che è un libro che cerca di scardinare l’idea che fare coding sia solo un’attività carina o che si debba fare solo per studiare le STEM; i codici sorgenti relativi, in Logo, Prolog e Kojo, sono nel sito di accompagnamento al libro e li possono scaricate tutti.

Bene, ora arriva la domanda cruciale: ma se usiamo un computer per tirar fuori tutte queste parole, non stiamo limitando la creatività nostra o dei bambini?
Io non credo. Per due motivi che mi pare possano fondarsi proprio su quello che Rodari ha scritto, come ho cercato di spiegare in “Rodari digitale“, l’ultima fatica di quest’anno.

Primo: la parte creativa non è quella di andare a cercare le parole, ma quella di costruire la storia. La ricerca delle parole è la parte meccanica, che richiede l’accesso ad un archivio di parole e alcune regole. Altrimenti basterebbe avere nove parole per fare una poesia (“Si sta come d’autunno sugli alberi le foglie”).

Secondo motivo: non sto proponendo di usare un programma bello e pronto, e nemmeno di cominciare da zero. Propongo di partire da un codice sorgente che funziona, ma di metterci le mani da subito. Cambiando le parole di partenza, cambiando le regole (per esempio: solo sostantivi o anche verbi? quali preposizioni?). Variando la tecnica: tre parole invece di due; oppure aggiungendo i prefissi e i suffissi, i diminutivi e vezzeggiativi.

Terzo motivo (non era previsto, ma lo aggiungo lo stesso): un computer sarà pure ottuso, ma non c’è limite alle cose creative che possono fare un umano e un computer, insieme.

Ambiguità felice dei linguaggi

Feb
23

C’è una barzelletta che gira da tempo sui programmatori, esseri inadatti al mondo reale. Dice così:

La mamma dice a Pierino: vai al mercato e compra 2 litri di latte. Se ci sono le uova, comprane 6.
Pierino va e torna con 6 litri di latte.
La mamma: Perché hai comprato 6 litri di latte?
Pierino: Perché c’erano le uova.

Finite le risate per la risposta di Pierino (che immaginiamo essere il risultato di una specie di programma: IF ci sono le uova THEN comprane 6), ci accorgiamo che il problema è in quella particella “ne”, che è un riferimento pronominale. Di quelle cose che abbiamo detto prima. Un link, una URL relativa.
In Italiano, di solito, si riferisce all’ultimo sostantivo utilizzato. Quando ce ne sono più di uno (di che? di sostantivi) di solito con un minimo di interpretazione si capisce a quale ci si riferisce.
Se la mamma avesse detto:
[…] Se c’è lo zucchero, comprane 6 litri.
un parlante Italiano avrebbe capito che il riferimento era al latte, perché sa che lo zucchero non si vende a litri.

E’ uno degli aspetti tipici del linguaggio naturale: un riferimento generico può essere comodo in molti casi, ma può creare dubbi in altri. Dubbi che vanno risolti con delle ipotesi, oppure nell’interazione (“Scusa, mamma: 6 di cosa?”).

Si dice che i linguaggi di programmazione, essendo “formali”, non soffrono di queste malattie, anzi sono stati costruiti apposto per esserne immuni. La barzelletta prende in giro proprio questa ottusità dei computer, dei linguaggi, dei programmi. I computer non interpretano i programmi, ma li eseguono rigidamente. Per cui niente libertà, niente interpretazione, niente poesia, solo correttezza e efficienza.

Ma siamo proprio sicuri che sia così? Facciamo un gioco: traduciamo la storiella in un linguaggio molto usato per il web, ovvero PHP (tranquilli: il discorso può essere seguito da chiunque, anche senza nessuna competenza informatica).

$lista = Array (
 latte => 1,
 uova => 6
 );

In questo frammento di codice sorgente viene creato un dizionario ($lista), cioè una set di dati organizzati per coppie chiave/valore (latte=1, uova=6).
Ci si mettono dentro le informazioni e poi si possono estrarre quando servono.
Scrivendo così:

 print_r($lista);

possiamo vedere cosa c’è dentro $lista:

Array
(
    [uova] => 6
    [latte] => 1
)

Oppure, volendo andare più in dettaglio:

 
print_r($lista[latte]);

cioè: scrivi sullo schermo il valore della chiave “latte” nell’array $lista.
Che è, ovviamente, 1.

Se però guardiamo cosa succede dietro le quinte, ci accorgiamo che l’interprete ha segnalato due errori veniali:

PHP Notice: Use of undefined constant latte - assumed 'latte'
PHP Notice: Use of undefined constant uova - assumed 'uova'

E’ un nostro errore di scrittura: le chiavi sono state scritte come se fossero costanti (cioè senza le virgolette che invece accompagnano le stringhe di caratteri), ma non esiste nessuna costante che si chiama latte, né uova. Ma cosa ha fatto l’interprete PHP, oltre a segnalare l’errore? Ha fatto un’illazione, cioè ha supposto che si volesse scrivere:

'latte' =>1,
'uova' => 6

che sembra in effetti l’interpretazione più ragionevole.

Se siamo bravi programmatori e programmatrici, una volta letta la segnalazione correggiamo il codice, e tutto fila liscio.
Anzi, per essere ancora più precisini, creiamo una costante (visto che ci era stato chiesto), ma le diamo un valore un po’ bizzarro:

define('latte',uova);

Cioè: abbiamo definito una costante che ha come nome “latte”, ma come valore “uova”.
Vi sembra confondente? Ma il linguaggi di programmazione sono precisi, no? Quindi nessun problema: da un lato la costante, dall’altro la chiave.
E infatti, se avessimo lasciato le cose come stavano, non ci sarebbero stati problemi. Ma noi abbiamo voluto essere rigorosi e abbiamo creato la costante E messo gli apici intorno alle chiavi.
Ora se chiediamo:

print_r(latte);

(ovvero: qual è il valore della costante “latte”?), otteniamo la stringa “uova”, come prevedibile; mentre se chiediamo di nuovo:

print_r($lista[latte]);

il risultato non è né “uova”, né 1 ma …  6 !
Il che naturalmente ha una sua logica. Si potrebbe dire che l’interprete ha usato il riferimento pronominale nella nostra richiesta, e ha interpretato la chiave dell’array $lista[latte] come la costante “latte” che era stata definita prima. Ma non è quello che volevamo dire. Insomma, dal nostro punto di vista,  si confonde e restituisce 6, cioè interpreta il codice come se avessimo scritto:

print_r($lista[uova]);

Proprio come Pierino.

Ora cambiamo l’ordine delle chiavi:

$lista = Array (
 'uova' => 6,
 'latte' => 1
 );

e chiediamo di nuovo:

print_r($lista[latte]);

Dovrebbe essere uguale a prima, no?
Eh no, adesso il valore restituito è tornato ad essere 1!
Meglio, dite? Insomma… se provate a scrivere:

print_r($lista);

vi accorgete del pasticcio:

Array
(
    [uova] => 1
)

La chiave latte è stata sostituita da uova (con valore 1) e la chiave uova, che avevamo inserito con valore 6, è stata cancellata.

Certo PHP non è un modello di precisione, per un linguaggio di programmazione. Ma insomma: anche un linguaggio di programmazione è soggetto ad una forma di ambiguità referenziale. E questo dipende, come abbiamo visto, dall’ordine in cui vengono inserite le informazioni nel testo.
Come in una qualsiasi lingua naturale…