Capire un testo: e che ce vo’?

Anteprima(si apre in una nuova scheda)

Cosa significa “capire un testo”?
Sono più importanti le conoscenze sull’argomento (il background) oppure quelle sintattiche? Conoscenze o competenze?
Quanto conta la capacita di analizzare un testo alla ricerca di segnali conosciuti, o la capacità di astrarre strutture da frasi anche quando non si conoscono tutte le parole? O di cogliere analogie e immaginare varianti possibili?
Quanto è importante fare esercizi di questo tipo?

Roba vecchia trita e ritrita, sui cui sono stati fatti infiniti studi ma su cui molti, a mio parere, parlano un po’ a casaccio.
Più difficile avere un’opionione quando si parla di linguaggi di programmazione e di lettura di codice sorgente. Che però è un’attività reale, utile e molto comune. I programmi non solo si scrivono e si eseguono, ma si leggono. Sapere leggere un codice sorgente è un’abilità (o competenza?) fondamentale. Magari poco studiata.
Facciamo un esperimento. C’è un lettore medio a cui viene presentato un testo. Senza spiegazioni, senza commenti, senza contesto.
Un po’ come si fa con gli esercizi di italiano. Il testo è il seguente:

def trovaNano(nano: String, setteNani: List[String]): Boolean = setteNani.contains(nano)

Supponiamo che il lettore non abbia conoscenza approfondita di programmazione, ma conosca la matematica. Per esempio, che conosca il significato di funzione e di parametro, e il significato di tipo di parametro.
Potrebbe capire che:
def è la dichiarazione di una funzione, il cui nome è trovaNano
– i parametri di questa funzione sono due: nano  e setteNani
– i parametri di questa funzione hanno un tipo: una stringa il primo e una lista di stringhe il secondo. Questo è un suggerimento sull’uso futuro di questo funzione: dovremo stare attenti a passargli dei dati che corrispondano a questi tipi
– il tipo del valore di ritorno (il risultato) è un altro suggerimento: la funzione restituisce true o false, quindi è una specie di test.

Il corpo della funzione è costituito dalla parte dopo l’uguale:

 setteNani.contains(nano)

Anche qui, senza particolari conoscenze, il lettore arriva a capire che
contains è una funzione già presente nel linguaggio, o definita altrove.
contains viene applicata ai due valori passati alla funzione, in un modo bizzarro ma che corrisponde abbastanza alla sintassi delle lingue naturali (soggetto-verbo-complemento).
Insomma: la funzione controlla se nano (che è il valore passato al primo parametro) è contenuto nella lista setteNani (il valore del secondo parametro) e restituisce true o false a seconda dei casi.

Può insomma immaginare come si userebbe questa funzione. Qualcosa di questo tipo:

trovaNano("Brontolo",List("Eolo","Mammolo","Dotto","Brontolo","Gongolo","Cucciolo","Pisolo"))

Quanto sono importanti le conoscenze della favola e del cartone Disney? Direi poco, ma un po’ conta senz’altro. Se il codice fosse stato:

def xyuw(werwer: String, dfsgaswerwer: List[String]): Boolean = dfsgaswerwer.contains(werwer)

di sicuro non sarebbe stato lo stesso, no?
Quanto è importante la conoscenza dell’inglese e della sua sintassi? Un po’. Almeno contains qualcosa ci ha detto.
Quanto la conoscenza di altri linguaggi formali? Se non si ha idea di cosa sia una funzione, e che una funzione ha bisogno di parametri e che restituisce un valore, non si capisce nulla. La matematica qui aiuta.

Lo stesso lettore, in seguito, si trova di fronte quest’altro testo:

def trovaNanoF(nano: String, setteNani: List[String]): Boolean = {
(
 for (n <- 0 to 6
 if setteNani(n) == nano
 ) yield "trovato"
).contains("trovato")
}

La lettura è un bel po’ più faticosa. Bisogna arrivare fino alla fine, magari rileggere più volte.
La prima riga è quasi uguale, salvo che il corpo della funzione stavolta è su più righe, inserite tra parentesi graffe {}. Questo non dovrebbe essere un problema per il lettore, che è abituato alle parentesi.
Poi c’è una paroletta magica, che il lettore avrà magari incontrato altrove: for. For si usa in quasi tutti i linguaggi per eseguire un ciclo, cioè una serie di operazioni ripetute.
Si capisce facilmente cosa fa questo ciclo: conta da 0 a 6, e per oguno dei nani della lista setteNani controlla che sia uguale al nome del nano passato alla funzione; se si, il ciclo restituisce “trovato”.
Qualche dubbio potrebbe esserci su quel doppio uguale (==) e sulla paroletta yield, che è si inglese, ma non proprio comunissima come for e if. Magari un dizionario inglese aiuta: raccogliere, rendere, produrre.
Se il lettore conta le parentesi tonde, si accorge che la parentesi che si apre prima di for viene chiusa dopo yield “trovato”. Dopo c’è una parola che ha già incontrato (contains) e di cui ormai sa il significato (un test che verifica se un valore è presente in una lista).
Ecco spiegato l’arcano: for produce una lista, e contains verifica che ci sia “trovato” in quella lista.

Il lettore curioso si domanda: perché usara una lista come risultato? E anche: perché scrivere una funzione di sette righe quando se ne poteva usare una di una riga sola? Beh – si potrebbe rispondere il lettore in quel dialogo interno che è così importante nella lettura –  perché avremmo potuto usare la funzione anche con una lista un po’ ingrossata di nani:

trovaNano("Brontolo",List("Eolo","Mammolo","Dotto","Dotto","Brontolo","Gongolo","Cucciolo","Brontolo","Pisolo"))

E avremmo potuto inventare una variante  in cui yield restituisca non vero o falso, ma un numero, cioè l’ordine del nano (o dei nani) trovati nella lista:

def trovaNanoF(nano: String, setteNani: List[String]): Seq[Int] = {
for (n <- 0 to 8
 if setteNani(n) == nano
) yield n
}

Quanto è stata difficile la comprensione? Non è tutto chiaro, ma insomma il grosso si capisce.
Almeno per chi abbia già letto o scritto codice in altre occasioni. Senza una conoscenza pratica di for e if, senza la capacità di fare analogie o di immaginare varianti… mmmh.

Infine, l’intrepido lettore si imbatte in questo testo:

def trovaNanoR (nano: String, setteNani: List[String]): Boolean = {
setteNani match {
 case List() => false
 case unNano::altriNani if unNano==nano => true
 case _::altriNani => trovaNanoR(nano,altriNani)
}
}

Avendo letto i testi precedenti (che ora fanno parte del background), il lettore può immaginare che la funzione faccia qualcosa di simile alla precedente, ma stranamente senza usare cicli. L’inglese non ci aiuta molto (match? case?). Cosa è quel List()? Se ha sviluppato una capacità di vedere le strutture facendo astrazione dai contenuti, il nostro lettore andrà a cercare frasi che hanno un andamento regolare, con ripetizioni. Magari ipotizzerà che è stata usata una struttura sintattica formata da:

X match {
 case a => ...
 case b => ...
}

Vedere questa struttura però non è facile, almeno se non si ha un editor che evidenzia la sintassi con i colori. Come non è facile intuire cosa faccia se non se ne ha un’idea precedente. Ancora più difficile è fare ipotesi di interpretazione del simbolo :: o della sequenza _::
E poi una stranezza: all’interno del corpo della funzione è riportato il nome della funzione stessa (trovaNanoR), il che sembra un errore o almeno un paradosso. Come fa una funzione ad essere definita sulla base di se stessa?
E dove è la parte che dice che la funzione restituisce qualcosa?

Insomma senza una conoscenza specifica di questo linguaggio (che, per soddisfare la vostra legittima curiosità, è Scala), o almeno di altri linguaggi funzionali, e della ricorsività, il lettore non sarà in grado di seguire il discorso, non sarà in grado di verificare se la funzione contiene errori prima di eseguirla. Anche se ha capito come si usa la funzione e che risultato produce. Insomma ha una comprensione solo parziale, che gli permetterà di usare la funzione ma senza averla capita e senza poterla modificare e adattare a contesti diversi.

Cosa se ne conclude?
Che parlare di conoscenze e competenze nella lettura è una cosa complessa e che non si risolve in quattro righe su un giornale.


Pubblicato

in

da

Tag: