Go - from Google



Strutture di controllo          

Naturalmente anche Go non si sottrae all'obbligo di proporre queste semplici ma utilissime strutture.
Iniziamo pertanto dalla più semplice, il classico

if - else

Il costrutto è semplice:

if condizione (booleana o logica){
istruzioni
}

oppure:

if condizione (booleana o logica){
istruzioni
} else {
istruzioni
}

e infine:

if condizione (booleana o logica) {
istruzioni
} else if condizione (booleana o logica) {
istruzioni
} else if {
......
} else {
istruzioni
}

Annotiamo che a differenza rispetto ad altri linguaggi  coppia di parentesi graffe è obbligatorio anche in presenza di una sola istruzione. Questo, si specifica anche sul sito ufficiale, favorisce la leggibilità del codice. Inoltre, altrettanto obbligatorio, deve essere rispettato un criterio per il posizionamento delle graffe stesse:

- la parentesi graffa di apertura { deve stare sulla stessa riga di if e/o else
- la parentesi graffa di chiusura } deve anch'essa stare sulla stessa riga delle istruzioni di selezione.
- la parentesi tonda indicata negli esempi non è obbligatoria, io la uso per abitudine e per chiarezza

Vediamo un piccolo esempio

  Esempio 3.1
1
2
3
4
5
6
7
8
9
10
package main
import "fmt"
func main(){
bool01 := false
if (bool01){
fmt.Printf("vero")
} else {
fmt.Printf("falso")
}
}

Come annotazione diciamo che Go permette di non utilizzare else in casi come questi:

if (condizione) {
return x
}
return y

ma non

if condizione) {
return x
} else {
return y
}

con il return ripetuto in entrambi i branch. Questo non va bene. Chiudiamo con un altro semplice esempio che può tornare utile (qualche volta...)

  Esempio 3.2
1
2
3
4
5
6
7
8
9
10
package main
import "fmt"
import "runtime"
func main(){
if (runtime.GOOS == "windows"){
fmt.Printf("Usi Windows")
} else {
fmt.Printf(".nix?")
}
}

Molto vicino, come logica, a if-else è l'altrettanto noto binomio:

switch - case


Questa istruzione in Go è più elastica rispetto ad altri linguaggi. Comunque vediamo subito il formato standard (ma non è l'unico...):

switch var {
case valore 1:
.....
case valore 2:
.....
default:
.....
}

differentemente da altri linguaggi var può essere di tipo qualunque e ovviamente anche i valori posso essere qualunque purchè coerenti col tipo. Nota importante: la parentesi graffa aperta { deve essere sulla stessa riga della parola switch. Ogni "case" rappresenta un branch (ramo, diramazione, braccio, in italiano ho sentito i termini più disparati per cui preferibilmente adopererò quello inglese, più univoco a mio avviso) del blocco mentre il caso identificato tramite la keyword default, che è opzionale, viene eseguito solo qualora nessun branch sia stato raggiunto. Da notare che il nostro default può essere messo ovunque nella sequenza dello switch ma la logica e la leggibilità suggeriscono naturalmente di metterlo in fondo.

  Esempio 3.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import "fmt"
func main(){
var y rune
y = 0;
switch y{
case 0:
fmt.Println("0")
case 1:
fmt.Println("1")
default:
fmt.Println("altro")
}
}

E' il momento di qualche osservazione:

** E' possibile inserire più istruzioni in ciascun branch. Non è necessario anche in questo caso l'uso delle parentesi graffe, anche se è esso è naturalmente consentito.
** Go permette in modo molto semplice di avere più alternative legate al medesimo case. In questo caso le alternative sono separate da una virgola:

case 1,2,3,4 :

** Interessante è anche notare che è escluso il fall-through, ovvero quel (perverso) meccanismo che permette l'esecuzione di più branch in sequenza. In C++ ad esempio il fatto di dimenticare un'istruzione break dà luogo proprio a questo comportamento, origine di bugs fastidiosi e pericolosi. Anche nel linguaggio D è così, pur se il compilatore è dotato di uno switch opzionale per segnalare questo rischio. Go, come altri linguaggi di default non permete il fall through che, per aver luogo, deve essere esplicitamente richiamato tramite l'istruzione fallthrough, appunto.

Vediamo il tutto riassunto in un esempio:

  Esempio 3.4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main
import "fmt"
func main() {
var num1 int
fmt.Print("Inserisci un numero da 1 a 10: ")
fmt.Scanln(&num1)
switch num1 {
case 0: fallthrough
case 1,2,3,4:
fmt.Print("Minore di 5")
case 5,6,7,8:
fmt.Print("da 5 a 8")
case 9,10:
fmt.Print("Sei al massimo")
default:
fmt.Print("Ho detto da 0 a 10\n")
fmt.Print("Asinello!!!")
}
}

Alla riga 8 fallthrough fa ricadere nel case definito alla 9.
In questa sua forma switch è perfettamente analogo a quanto si incontra in altri linguaggi. Tuttavia in Go è possibile non specificare nulla a livello dell'istruzione switch lasciando al case l'onere della valutazione, quindi:

switch {
case condizione 1:
istruzioni
case condizione 2:
istruzioni
.....
default
istruzioni
}

ad esempio:

switch {
case(x < 0):
fmt.Print("aaa")
case(x == 0):
fmt.Print("bbb")
case(x > 0):
fmt.Print("ccc")
}

come si vede nel frammento precedente la valutazione della condizione avviene a livello dei case.
Le cose non finiscono qui ed abbiamo anche una terza forma di espressione legata a switch:

switch inizializzazione; {
case valore 1:
.....
case valore 2:
.....
default:
.....
}

noterete il punto e virgola posto nella prima riga. Esempio:

  Esempio 3.5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main
import "fmt"
func main() {
var n1 int
var n2 int
fmt.Print("Inserisci un numero da 1 a 10: ")
fmt.Scanln(&n1)
fmt.Print("Inserisci un numero da 1 a 10: ")
fmt.Scanln(&n2)
switch result := n1 + n2; {
case result / 2 == 0:
fmt.Print("Somma pari")
case result / 2 != 0:
fmt.Print("Somma dispari")
}
}

Comunque si tratta di una feature semplice ma praticamente ed espressivamente molto comoda.

Detto di switch l'altro importante strumento di controllo e, nel caso specifico, di iterazione, è il noto for. Il formato più consueto è il seguente, molto simile ad altri linguaggi:

for inizializzazione; condizione; modificatore { }

a proposito di questo è da notare che non vi sono parentesi tonde che circondano inizializzazione, condizione e modificatore, come invece si ha in altri linguaggi; in Go questa sintassi non è permessa, quindi niente tonde. Come si vedrà meglio nell'esempio seguente, per evitare che il compilatore consideri chiusa l'istruzione al termine della prima riga, la graffa di apertura deve essere posta nella stessa riga del for.

  Esempio 3.6
1
2
3
4
5
6
7
package main
import "fmt"
func main() {
for n1 := 0; n1 < 5; n1++ {
fmt.Print("numero ", n1, "\n")
}
}

Il modificatore può essere incrementale o decrementale e il passo di incremento o decremento può assumere valori diversi da 1, per esempio, in ossequio a quanto appena detto, potrebbe essere possibile modificare la riga 4 nei due modi seguenti:

for n1 := 5; n1 > 0; n1--
for n1 := 0; n1 < 5; n1 = n1 + 2


Una osservazione ulteriore riguarda la visibilità di delle variabili definite nel loop. Nell'esempio 3.6 n1 è visibile solo dentro le graffe che delimitano il corpo del for; questa è una regola avente valenza generale. Variabili definite in quell'ambito risultano non definite all'esterno di esso. Anche in questo caso vi è piena analogia con altri linguaggi.
All'interno del for è possibile lavorare con più contatori, anche se non so quante volte vi capiterà

  Esempio 3.7
1
2
3
4
5
6
7
package main
import "fmt"
func main() {
for i,j := 0,5; i < j; i,j = i+1, j-1 {
fmt.Print(i + j, "\n")
}
}

Abbiamo finora parlato del comando for nella sua forma più comune. Ne esiste tuttavia un'altra che prevede che la condizione sia legata direttamente al for. Chiariamo la cosa con un esempio:

  Esempio 3.8
1
2
3
4
5
6
7
8
9
10
package main
import "fmt"
func main() {
var x int = 0
for x < 10 {
fmt.Print(x, "\n")
x++
}
fmt.Print(x)
}

La riga 5 illustra il concetto, di fianco al for abbiamo una condizione booleana. La riga 9 ci ricorda che, in questo caso, la visibilità della variabile usata all'interno del for c'è anche all'esterno (come è banale che sia visto che lì è stata creata).
Un altro modo di utilizzare for è attraverso la definizione di un range (è la strada seguita ad esempio nel nuovissimo linguaggio Ceylon). Si tratta di una variante elegante di iterare su array e altri costrutti sequenziali. Il formato tipico è il seguente:

for var, valore := range, collezione {
.....
}

* var è la variabile ed è una copia del valore nell'indice corrente
* valore è una copia (quindi non è l'originale) del valore ad un dato indice della collezione.
* range indica che ci stiamo occupando di una cerca sequenza di valori
* collezione può essere un array, una stringa ecc...

  Esempio 3.9
1
2
3
4
5
6
7
8
package main
import "fmt"
func main() {
var s string = "abcdef"
for x, char := range s {
fmt.Print(x, " ",char, "\n")
}
}

Può essere utile in alcune situazioni.

Il loop infinito è ottenibile in vari modi ovviamente. Uno dei più semplici è scrivere

for ;; {
.....
}

in questo caso attenzione all'uso del tool di formattazione chiamato gofmt, il formattatore incluso nelle distribuzioni di Go, che tende a scartare questo tipo di istruzione, oppure anche così:

for i:= 0; ; i++ {
....
}

semplicemente non introducendo alcuna condizione. Ovviamente si possono scrivere condizioni assurde che non verificano ma naturalmente non è buona pratica. Un ciclo infinito può essere interrotto in una appicazione console da un CTRL-Z o un CTRL-BREAK o in altri modi. Un sistema può passare attraverso l'istruzione break. Lo scopo di questa è quella di uscire bruscamente dal loop. Esempio:

  Esempio 3.10
1
2
3
4
5
6
7
8
9
10
package main
import "fmt"
func main() {
for i:= 0; i > -1; i++ {
fmt.Print(i, "\n")
if i == 10 {
break
}
}
}

Questo semplice programma presenta una (brutta, lo so ;-) la uso solo per aggiungere un esempio in più a quanto detto appena sopra) condizione di uscita che non si verifica mai. Avremmo dunque un incremento infinito di i che porterebbe ad un overflow prima o poi. Inserendo un break alla riga 7 facciamo si che al raggiungimento del valore 10 il programma si interrompa automaticamente. Stesso effetto ha l'istruzione return la quale però causa un salto fuori dalla corrente funzione (quindi nel caso del main termina il programma). Comportamento diverso invece è quello di continue che ha l'effetto di saltare il codice posto dopo di essa restituendo il controllo all'iterazione successiva.

  Esempio 3.11
1
2
3
4
5
6
7
8
9
10
package main
import "fmt"
func main() {
for i:= 0; i < 10; i++ {
if i == 5 {
continue
}
fmt.Print(i, " ")
}
}

L'output dell'esempio è:

0 1 2 3 4 6 7 8 9

noterete che manca il 5 in quando le sitruzioni dalla 5 alla 7 implicano che quando la variabile "i" raggiunge quel valore il controllo ritorni all'inizio dell'iterazione.

Sia break che continue possono essere usate in congiunzione con una label. Questa ha una comportamento diverso dal goto, che vedremo qui di seguito, ovvero, non è un goto. Potete provarne l'uso con il seguente esempio guardando a come esso funziona togliendo la label stessa:

  Esempio 3.12
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import "fmt"
func main() {
lista1 := []rune{1,2,3}
lista2 := []rune{2,3,4}
Z:
for _, x := range lista1 {
for _, y := range lista2 {
fmt.Print(x + y, "\n")

if y == x {
fmt.Print(y,"\n")
continue Z
}
}
}
}


Per concludere il paragrafo citiamo la possibilità di usare, come anticipato qui sopra, la coppia goto + label. Come anche altrove ho puntualizzato si tratta dell'istruzione più esecrata del mondo della programmazione. Nonostante ciò quasi tutti i linguaggi di programmazione la supportano, obtorto collo probabilmente, ma la supportano. E anche Go, come detto, non fa eccezione. Tra l'altro, da qualche parte ho letto che il compilatore stesso di Go internamente ne fa uso.... Darò un esempio:

  Esempio 3.13
1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main() {
x := 0
SALTO:
fmt.Print(x)
if x == 5 {
return
}
x++
goto SALTO
}

L'etichetta va sempre messa in maiuscolo, semplice ma efficace accorgimento per migliorarne la visibilità all'interno del codice. Come è evidente la leggibilità del codice non è gran che già in questo semplice esempio. Personalmente sconsiglio anche io l'uso del goto, senza però demonizzarlo; ha una sua efficacia espressiva che in casi molto particolari può tornare utile.

Noterete l'assenza di istruzioni quali while, do... while che sono presenti in altri linguaggi. Go non le prevede mentre, come visto, compensa queste mancanze con una maggior flessibilità di altri costrutti. Dal punto di vista della potenza espressiva non manca nulla in pratica.