Ruby language

Controllo di flusso               

Non può mancare il capitolo dedicato a queste istruzioni sempre presenti, Anche se un po' noioso per chi già ha esperienza in altri linguaggi, si tratta comunque di un piccolo male necessario.... comunque, come vedremo, in questo linguaggio c'è qualche interessante peculiarità.

Iniziamo quindi dalle istruzioni condizionali, dipendenti quindi istruzioni la cui esecuzione è legata selettivamente al verificarsi di una specifica condizione. La prima di queste è il ben noto
if
che in Ruby ha, nella sua forma più semplice e comune, questa sintassi:

if espressione
istruzioni
end

  • il blocco di istruzioni viene eseguito se e solo se l'espressione non è false o nil

  • il blocco va separato fisicamente dall'espressione tramite un "a capo", cioè non deve stare sulla stessa riga. Diversamente bisogogna inserire la keyword then

  • l'espressione condizionale non necessita di parentesi (al contrario di molti altri linguaggi)

  • end è obbligatorio per chiudere il blocco che inizia con if.

in base a quanto detto al punto due esiste una sintassi alternativa:

if espressione then istruzione end

nel caso di più istruzioni esse vanno separate col punto e virgola

  Esempio 6.1
1
2
3
4
5
6
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
if n1 % 2 == 0
puts "hai inserito un numero pari"
end

L'esempio mostra il nostro if in azione. La condizione verifica se il numero inserito è divisibile per 2; in caso affermativo (quindi l'espressione espone il valore true, viene eseguito il blocco (una sola istruzione, ok) alla riga 5, diversamente il programma termina senza nulla fare. Naturalmente il blocco può essere costituito da più istruzioni. Ad esempio, utilizzando la key word then vista prima si poteva scrive una cosa così:

if n1 % 2 == 0 then puts "hai inserito un numero pari"; puts "Ciao"

Mi pare che molti programmatori preferiscano mettere comunque il then, anche quando vanno a capo in quanto renderebbe il codice più robusto, nel senso che se anche viene rimosso il newline non viene generato alcun errore. Tutto sommato mi sento di condividere questa impostazione.

L'istruzione if non finisce qui naturalmente ed è arrivato in momento della keyword else con la quale si introduce una alternativa (o più) al ramo principale.

if espressione
istruzioni-1
else
istruzioni-2
end

La logica è evidente: se si verifica quanto indicato nella espressione del ramo che inizia con if viene eseguito il gruppo di istruzioni-1 altrimenti va in scena il gruppo di istruzioni-2. In questo caso else non necessita di then o di newline prima del proprio gruppo di istruzioni. Else ha il ruolo di "rastrello" in quanto raccoglie tutte le possibilità che non sono state prese in esame nei brach precedenti, come vedremo anche nel prossimo passo. Intanto ecco un semplice esempio:

  Esempio 6.2
1
2
3
4
5
6
7
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
if n1 % 2 == 0 then
puts "hai inserito un numero pari"
else puts "non hai inserito un numero pari"
end

Se le istruzioni sono più di una, metterle una sotto l'altra è ovviamente una buona regola per la leggibilità del programma.

La terza incarnazione del nostro if prevede la presenza di elsif. Sia else che elsif sono opzionali, come si evince da un semplice ragionamento.

if espressione
istruzioni-1
elsif espressione
istruzioni-2
elsif espressione
.......
else
istruzioni-n
end

elseif viene usato per introdurre espressioni alternative a quella presentata nel ramo relativo a if. Mentre può esserci un solo ramo che iniza con if ed uno solo che inizia con else possono essercene quanti vogliamo che iniziano con elsif, come risulta dalla definizione formale qui sopra. Anche per elsif valgono le regole che abbiamo visto per if ovvero deve esserci un carattere di newline oppure un then prima del blocco delle istruzioni. Anche in presenza di elsif la clausola else mantiene il suo ruolo e raccoglie tutte le alternative non comprese dalle casistiche che lo precedono.

  Esempio 6.3
1
2
3
4
5
6
7
8
9
10
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
if n1 % 2 == 0 then
puts "hai inserito un numero pari"
elsif n1 < 0 then
puts "hai inserito un numero negativo"
else
puts "hai inserito un numero dispari"
end

E' interessante notare che la nostra istruzione if, anche comprensiva di elsif ed else, in Ruby è un po' più potente di quanto sia in molti altri linguaggi. In questo caso siamo di fronte ad una vera e propria espressione che esprime un valore di ritorno che può essere nil se nessun ramo si verifica oppure il risultato di quello che viene eseguito. In questo senso si possono scrivere espressioni che risultano più eleganti e concise ad esempio:

destinazione = if scelta == 1 then "Roma"
elsif scelta == 2 then "Parigi"
elsif scelta == 3 then "Berlino"
elsif scelta == 4 then "Stoccolma"
else "casa"
end

Sempre in tema di capacità espressiva abbiamo la possibilità di usare if in maniera alternativa, vediamo come.
La forma tipica abbiamo visto essere tipo:

if espressione
istruzioni
end

molto bene. Ruby ci permette anche di condizionare l'esecuzione di un comando al verificarsi di una codizione utilizzando if come terminatore. In pratica:

istruzione if espressione

dando quindi maggior enfasi alle istruzioni da eseguire piuttosto che alla condizione, cosa utile specialmente se la stessa è banale. L'esempio dovrebbe chiarire la cosa, premettendo che:
1) quando più di una istruzione esse vanno separate con il punto e virgola tranne l'ultima, quella che precede il nostro if
2) non bisogna mettere alcun newline tra la sequenza delle istruzioni e if, insomma tutto si volge su una singola riga
3) questa sintassi non permette l'uso di elsif - else
4) l'espressione sottesa da if viene comunque valutata prima di eseguire le istruzioni.

  Esempio 6.4
1
2
3
4
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
puts "Bravo!"; puts "Bene" if n1 % 2 == 0

Ovviamente se le istruzioni sono molte è meglio fare nel modo classico perchè si perderebbe il vantaggio di questo sistema ovvero la leggibilità.
Questo sistema espone a qualche piccolo rischio, attenzione a quel che fate:

  Esempio 6.5
1
2
3
4
5
6
7
8
9
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
y = 10
y = 3 if n1 % 2 == 0
puts y
z = 10
z = (3 if n1 % 2 == 0)
puts z

Vediamo l'output nel caso in cui si inserisca un numero pari:

Inserisci un numero: 4
3
3

e nel caso in cui inseriamo un numero dispari:

Inserisci un numero: 5
10
 

Nel primo caso va tutto bene nel secondo non è che manchi il valore di y è che esso è stato portato a nil. Infatti alla riga 8 viene forzato un assegnamento al valore risultante dalla espressione mentre la riga 5 non forza l'assegnazione se non accade nulla, come nel caso in cui inseriamo in input un numero dispari. Dietro tutto c'è il fatto if ha una minor forza (precedenza) rispetto all'operatore di assegnamento.

Come alternativa al classico if segnalo anche la presenza in Ruby, come in molti altri linguaggi, del classico operatore ternario ? :

stringa  = x < 10 ? "singola cifra" : "doppia cifra"

equivalente a;

if x < 10 then
stringa = "singola cifra"
else
stringa = "doppia cifra"

Una scrittura compatta ed elegante che aiuta a ridurre la digitazione in casi semplici.

Come nota a margine segnalo la possibilità di ridurre l'uso degli if ricorrendo alla classe hash. La logica è semplice e vale bene per i casi in cui la selezione di un ramo o dell'altro serva ad effettuare selezioni ristrette (ma anche con sequenze di istruzioni si potrebbe ricorrere alle funzioni... lo vedremo più avanti): i valori delle espressioni sono le chiavi di un hash mentre le istruzioni sono i valori corrispondenti. Si prenda il seguente esempio:

  Esempio 6.6
1
2
3
4
5
6
7
8
9
print "Inserisci un numero (1 o 2 o 3 o 4): "
s1 = gets
n1 = s1.to_i
s2 = if n1 == 1 then "uno"
elsif n1 == 2 then "due"
elsif n1 == 3 then "tre"
elsif n1 == 4 then "quattro"
end
puts s2

Potremmo anche riscriverlo come segue, usando appunto la classe hash:

  Esempio 6.7
1
2
3
4
5
6
print "Inserisci un numero (1 o 2 o 3 o 4): "
s1 = gets
n1 = s1.to_i
h1 = {1 => "uno", 2 => "due", 3 => "tre", 4 => "quattro"}
s2 = h1[n1]
puts s2

Cosa preferite? Forse l'esempio 6.6 è un po' più leggibile mentre il 6.7 è più sintetico. A voi la scelta.

Ultima osservazione: come si comporta Ruby di fronte al famoso, comune, errore che tanti mal di pancia ha causato e causa a molti programmatori, di usare l'operatore di assegnazione invece di quello di uguaglianza in una espressione? Ovvero, se scriviamo
if x = 0 then .... invece di
if x == 0 then.......
possiamo avere dei problemi? La risposta è ni. In alcuni linguaggi questa è una espressione lecita (C/C++, per fare un esempio) e allora i guai arrivano in altri (C#, Java, D) viene segnalato errore, ed è cosa buona, secondo me. Ruby sceglie una strada mediana ovvero vi fa comparire una segnalazione di warning come segue

warning: found = in conditional, should be ==

L'esecuzione però procede..... quindi attenzione.

Dopo aver sviscerato if possiamo passare al suo opposto, logicamente parlando, ovvero unless. Dato che if può essere impostato in vari modi forse di unless si potrebbe fare a meno, nel senso che non è un costrutto strettamente necessario essendoi già il più comune, ne mondo informatico, if. Tuttavia averlo è una possibilità espressiva in più. Questa istruzione vede eseguito il proprio codice sottostante solo nel caso in cui la condizione sia falsa o nil (proprio l'opposto di quanto visto per if). La sintassi formalmente è la seguente potendosi presentare in 3 modalità che ricordano in parte quelle dell'istruzione if:

unless condizione
istruzioni
end

oppure

unless condizione
istruzioni
else
istruzioni
end

o anche

istruzioni unless condizione

come avrete intuito anche in questo caso dopo la condizione bisogna andare a capo a meno di non ricorrere a then. Valgono insomma le stesse identiche regole sintattiche che abbiamo visto per l'istruzione if. Anche con unless io preferisco mettere sempre e comunque then. Vediamo ora un esempio per ciascuno dei 3 casi:

  Esempio 6.8
1
2
3
4
5
6
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
unless n1 < 0
puts n1
end
  Esempio 6.9
1
2
3
4
5
6
7
8
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
unless n1 < 0 then
puts n1
else
puts "numero < 0"
end
  Esempio 6.10
1
2
3
4
5
6
7
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
destinazione = unless n1 < 0 then "Italia"
else "Resto del mondo"
end
puts destinazione

Quello che senz'altro avrete notato è la mancanza di qualche cosa che equivalga alla keyword elsif anche nel caso di unless. Bene questo equivalente non c'è e quindi per casi complessi dovrete un po' farvi di bocca buona e prepararvi ad una certa verbosità:

  Esempio 6.11
1
2
3
4
5
6
7
8
9
10
11
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
unless n1 <= 0 then
puts "maggiore di zero"
else unless n1 == 0 then
puts "minore di zero"
else
puts "xero"
end
end

Quindi bisogna utilizzare una forma mista che apre altri else, che possono essere quanti ne vogliamo, ricordandosi poi di chiuderli con altrettanti end. Anche in presenza di unless vale la considerazione finale fatta con if relativamente all'uso di = invece di ==; ne viene fuori un warning.

Proseguiamo ora il nostro cammino incontrando l'istruzione case.
Diciamo subito che, teoricamente parlando, di case si potrebbe anche fare a meno. Tuttavia ha un suo potere espressivo che lo rende molto elegante in certi casi e quindi il fatto di averlo è un plus positivo. Come ben evidenziato dall'autore di Ruby in persona nel suo ottimo testo sul linguaggio, una vera Bibbia in materia, questa istruzione ricalca a volte molto da vicino il comportamento di if (come del resto in tutti gli altri linguaggi a me noti.... è per quello che se ne potrebbe anche fare a meno)

  Esempio 6.12
1
2
3
4
5
6
7
8
9
10
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
stringa = case
when n1 == 1 then "uno"
when n1 == 2 then "due"
when n1 == 3 then "tre"
else "altro"
end
puts stringa

La sintassi formale è chiara:

case
when
....
when
else
end

Laddove le clausole when possono essere numerose a piacere mentre else, che come al solito funge da "scopa" per tutti i casi eventualmente non compresi nei rami precedenti e quindi deve essere messo in coda, è opzionale ed unico. Non è, per fortuna, presente la possibilità del fall-through, ovvero non esiste il rischio che siano eseguiti più rami (vedasi quanto avviene in C++ o anche C#, per citarne un paio se dimenticate la parola break). L'esempio 6.12 ricorda da vicinissimo il 6.6 ed in effetti in questo caso la seqeunza case-when è solo un alias di if-elsif. In maniera del tutto analoga si può notare che anche case restituisce un valore assegnabile ad una variabile (va da sè che se non esiste clausola else e nessun ramo when viene soddisfatto il valore restituito è nil) mentre, anche qui in analogia con quanto valido per if, la parola then può essere sostituita con un "a capo" (e anche quindi io consiglio invece di metterla). La parola when può essere seguita anche da più di una condizione e il separatore può essere la virgola. In altre parole possiamo scrivere:

when x == 1, x == 2, x == 3 then

basta che almeno una delle condizioni sia verificata (in questo caso non può essere che una) e il branch viene eseguito. Matz nel suo testo afferma, forse con ragione, che è meglio scrivere:

when x == 1 || x == 2 || x == 3 then

usando l'operatore logico || (che sta per "or")

Il nostro case comunque non è solo un doppione di if. Esiste un altro formato che risulta più espressivo ed è molto più utilizzato perchè più sintetico:

case variabile
when valore1
istruzioni
when valore2 then istruzioni
when valore3; istruzioni
else
istruzioni
end

In questa definizione formale sono anche illustrate le possibili modalità di uso di when:
1) con il carattere "a capo" - in corrispondenza a valore1
2) con la keyword then - in corrispondenza a valore2
3) con un ; che comunque le veci di "a capo" - in corrispondenza a valore3
Questa nuova definizione ci permette di riscrivere l'esempio 6.12 come segue;

  Esempio 6.13
1
2
3
4
5
6
7
8
9
10
print "Inserisci un numero: "
s1 = gets
n1 = s1.to_i
stringa = case n1
when 1 then "uno"
when 2 then "due"
when 3 then "tre"
else "altro"
end
puts stringa

Come detto si potevano scrivere le cose anche in altro modo ad esempio

when 3; "tre"
come visto nella definizione formale.

Usando lo stile dell'esempio 6.13 la variabile n1 viene valutata una volta sola e poi viene confrontata con i valori proposti. Anche in questo caso ovviamente non esiste il problema del fall-through che Ruby esclude categoricamente, per fortuna,
In molti testi si fa riferimento al fatto che Ruby usa l'operatore === internamente al case. Si tratta dell'operatore di uguglianza valido anche per le classi. In pratica, visto che tutto in Ruby è un oggetto, è il vero operatore di uguaglianza. Si tratta anzi di un metodo, tanto che in realtà scrivere
x === 1 è una pura abbreviazione sintattica per
x.===(1)
A questo livello è una informazione poco utile ma, come si dice, tenetela nel cassetto....

Può capitare di dover usare intervalli di valori molto ampi o comunque tali da essere scomodi da gestire valore per valore. In questo caso possono venire utili i range, di cui parleremo nel capitolo ad essi dedicato, ma diamo comunque un anticipazione formale qui di seguito tramite un esempio:

case x
when 1..10 puts "Prima decina"
when 11..20 puts "seconda decina"
when 21..30 puts "terza decina"

E' venuto ora il momento di parlare delle istruzioni iterative.

Il primo incontro lo facciamo con il classico while:

while condizione [do]
istruzioni
end

La condizione deve essere preventivamente inizializzata in quanto prima di entrare per eseguire le istruzioni viene compiuta una sua valutazione. Se e finchè la condizione è vera il gruppo di istruzioni (una o n) viene eseguita. Ad esempio possiamo scrivere:

  Esempio 6.14
1
2
3
4
5
x = 10
while x > 0 do
puts x
x = x - 1
end

Questo breve programma espone a video i numeri da 10 a 1. La riga 1 ci presenta l'inizializzazione del valore che sarà usato nella condizione. Dimenticare questa riga dà origine ad un errore a runtime. La riga 4 ci mostra che tale condizione deve essere in qualche modo variata altrimenti il ciclo non finirebbe mai, come potete comprovare eliminando la riga 4 stessa. Ovviamente la condizione alla riga 2 deve essere vera almeno al primo accesso altrimenti il corpo del while non viene mai eseguito.
La parentesi quadra nella definizione formale indica che il do è opzionale. Io preferisco metterlo, anche per la mia estrazione "pascaliana" ma potete scegliere secondo il vostro gusto se usarlo, limitarvi ad un newline o anche mettere un ; (quest'ultima secondo me è la soluzione più innaturale).

Questa istruzione non presenta soverchie difficoltà così possiamo subito passare a vederne l'uso come modificatore:

  Esempio 6.15
1
2
3
4
5
x = 10
begin
puts x
x = x - 1
end while x > 0

La cosa importante è che while sia sula stessa riga dell'istruzione end. Se ci fosse un newline di mezzo l'interprete la identificherebbe come un nuovo loop indipendente e molto probabilmente capitereste in un loop infinito, visto che la condizione non sarebbe modificata.

Molto vicina al while è until. Mentre while continua la sua esecuzione finchè la condizione è vera until va in scena fin quando la condizione non è verificata. La sintassi è banale:

until condizione [do]
istruzioni
end

Anche in questo caso do è opzionale e può essere sostituito dal ; o da un newline.
Vediamo subito l'esempio:

  Esempio 6.16
1
2
3
4
5
x = 10
until x < 0 do
puts x
x = x - 1
end

quindi, come scritto alla riga finchè x non diventa minore di 0 vengono eseguite le istruzioni; oppure potete leggere la condizione come "finche x < 0 è falso" mentre, per esempio la condizione alla riga 2 dell'esempio 6.14 recita "finchè x > 0 è vero". In questo sta la differenza tra while e until molto simile ma in realtà opposti come logica.
Anche until può essere usato in forma di modificatore:

  Esempio 6.17
1
2
3
4
5
x = 10
begin
puts x
x = x -1
end until x < 0

e anche in questo caso until va messo sulla stessa riga in cui si trova end.

Istruzione di grande importanza è il for, che introduce una iterazione compiuta un certo numero di volte (anche 0 o infinito) come il while ma che presenta una casistica un po' più varia anche perchè in realtà, come vedremo, non vive di vita propria ma piuttosto maschera qualche altra cosa...
Il formato di questa istruzione è:

for variabile in collezione do
istruzioni
end

Il punto critico è capire cosa si intende con "collezione". In generale si può dire che intendiamo, in questo ambito, qualsiasi oggetto sul quale sia applicabile il metodo each. Tipico ad esempio è un array oppure un un hash, oppure un range. Vediamo quindi un esempio:

  Esempio 6.18
1
2
3
for x in 1..10 do
puts x
end

semplice vero? In pratica x itera all'interno di un range che va da 1 a 10 e la riga 2 stampa a video tale valore. Ovviamente, come detto, si poteva provare ad usare un array:

  Esempio 6.19
1
2
3
4
ar1 = [1,3,5,7,9]
for x in ar1 do
puts x
end

Concettualmente non cambia nulla, viene effettuata una iterazioni sugli elementi dell'array.

Il fatto che for sia applicabile laddove each è un metodo valido per la collezione dipende dal fatto che questa istruzione è in pratica uno shortcut per:

collezione.each do |x|
istruzioni
end

come è semplice comprovare. Le due scritture, quella col for e quella diretta cche usa esplicitamente each, sono simili direi del tutto identiche anche se la seconda crea un nuovo scope per le variabili.
Una delle domande che spesso si fanno è quella relativa al passo, lo step della iterazione. Ovvero il poter iterare tra gli elementi, ad esempio, del programma 6.18 ma voglio un passo diverso da uno, come è possibile in tuti i linguaggi. Un sistema è quello di usare step tramite il quale la riga 1 proprio del 6.18 verrebbe modificata così:

for x in (1..10).step(2) do

ed usciranno i numeri 1,3,5,7,9. Se invece usiamo l'altro sistema sparisce la parola each e resta step:

(1..10).step(2) do |x|

Una piccola considerazione la merita proprio la classe range. Essa rappresenta un insieme di valori, una sequenza che vanno da un minimo ad un massimo. La vedrete nel prossimo paragrafo in dettaglio, se volete potete andare a consultarlo per avere qualche approfondimento in più.

Vediamo ora un esempio che contempla l'iterazione con la presenza di un hash:

  Esempio 6.20
1
2
3
4
h1 = {1 => "uno", 2 => "due", 3 => "tre"}
for chiave, valore in h1 do
puts "#{chiave} => #{valore}"
end

Come si vede è possibile definire più valori sui quali operare separandoli con un virgola.
Vediamo anche un esempio di cicli for innestati; l'indentazione ci aiuta in questo caso a capire meglio le cose mentre va ricordato che la keyword end di chiusura si riferisce al for più vicino non ancora chiuso.

  Esempio 6.21
1
2
3
4
5
for x in (1..10) do
  for y in (3..8) do
    puts x * y
  end
end

Prima di chiudere dobbiamo anche dare uno sguardo ad un altro tipo di iterazione che è quella che si costruisce a parte dai numeri interi. Tra i vari metodi a disposizione di questa classe abbiamo times che ci permette di impostare codice come il seguente:

irb(main):001:0> 3.times {puts "ciao"}
ciao
ciao
ciao

oppure anche usando upto

irb(main):003:0> 2.upto(5) {puts"ciao"}
ciao
ciao
ciao
ciao

o downto:

irb(main):004:0> 4.downto(2) {puts"ciao"}
ciao
ciao
ciao

Questa tipologia di iterazione è molto comune in Ruby, anzi si può affermare, nonostante la presenza degli altri costrutti visti in questo paragrafo, che sia la modalità più comune di realizzare dei loop in questo linguaggio. Stiamo parlando degli iteratori ovvero una delle feature più interessanti di Ruby.

L'uso più comune di questi costrutti, in presenza di più istruzioni fa omunque uso della sequenza do..end.

  Esempio 6.22
1
2
3
4
4.times do
puts "ciao"
puts "Hello"
end

Questo vale quindi sia per upto che per downto e anche per map.

Dunque, cosa sono gli iteratori. Essi, in maniera trasparente, fanno uso di un meccanismo piuttosto complesso che si basa sull'istruzione yield. Questa si occupa di trasferire il controllo dall'iteratore al blocco. Quando questo termina il controllo ritorna all'iteratore. Questo discorso vale per tutti gli iteratori costruiti anche a partire da sequenze e gestiti tramite each, li abbiamo già incontrati:

(1..10.each {|x| .....}

Comunque gli iteratori sono oggetto di apposito paragrafo.

Concludiamo questo paragrafo ricordando altre due keyword piuttosto utile ovvero break e next. La prima ha lo scopo di interrompere un loop passando il controllo al livello superiore (e se l'iterazione è già al livello più alto terminando il programma), mentre il secondo termina il ciclo corrente e passa al successivo sempre rimanendo all'interno del loop. Nell'esempio vediamo i azioni queste due istruzioni evidenziandone le ovvie differenze nell'output:

  Esempio 6.23
1
2
3
4
5
6
for i in 1..10 do
  if i == 6 then
  next
  end
print i," "
end
  Esempio 6.24
1
2
3
4
5
6
for i in 1..10 do
  if i == 6 then
  break
  end
print i," "
end

1 2 3 4 5 7 8 9 10
1 2 3 4 5

Come si vede, nel primo caso l'iterazione arriva fino alla fine ma manca il numero 6. Infatti next, alla riga 3 dell'esempio 6.23, istruisce l'interprete a saltare all'iterazione successiva di modo che l'istruzione di scrittura alla riga 5 non può essere eseguita.
Nell'esempio successivo invece il break invocato quando il contatore raggiunge il valore 6 interrompe il loop e quindi l'ultimo numero stampato è il 5.