Go - from Google



Funzioni   (1.)       

Anche Go non si sottrae ad un uso accentuato delle funzioni ed anzi questi costrutti sono molto potenti e flessibili in questo linguaggio.
Possiamo dividere le funzioni usate in Go in 3 categorie:

** normali
** anonime (lambda)
** metodi

In termini di definizione non troviamo molto di diverso rispetto agli altri linguaggi e nella sua forma più semplice possiamo esprimerla come segue:

func nomefunzione (parametri opzionali) {
corpo della funzione
}

la parentesi graffa di apertura deve stare oggligatoriamente nella stessa riga della definizione della funzione (ci avete fatto caso a proposito della funzione "main"?). Ovviamente func è la keyword che introduce una funzione.
Andiamo subito con un semplice esempio che ci mostra anche come richiamare una funzione:

  esempio 4.1
1
2
3
4
5
6
7
8
9
package main
import "fmt"
func main() {
scrivi()
}

func scrivi() {
fmt.Println("ciao")
}

La funzione scrivi è definita a partire dalla riga 7 ed è richiamata alla 4 (essendo un linguaggio compilato non c'è problema in termini di precedenze). L'esempio è estremamente semplice. Una funzione in realtà può avere dei parametri in ingresso e può restituire uno o più valori. Come in altri linguaggi la parola chiave per indicare il valore o i valori di ritorno è return. I parametri in ingresso invece devono essere compatibili per numero, tipo ed ordine con quelli della firma della funzione. L'esempio che segue è piuttosto interessante per vari motivi:

  esempio 4.2
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
var num2 int
fmt.Print("Inserisci un numero: ")
fmt.Scanln(&num1)
fmt.Print("Inserisci un numero: ")
fmt.Scanln(&num2)
x := max(num1, num2)
fmt.Print("Il massimo e': ", x)
}

func max(x int, y int) int {
if x > y {
return x
}
return y
}

Le righe in rosso sono particolarmente significative:
la 10 ci dice che le funzioni sono elementi di prima classe in Go e possono essere assegnate anche direttamente alle variabili come valori normali. Inoltre ci insegna come richiamare delle funzioni tramite parametri (non è l'unica forma, come vedremo tra breve).  La riga 14 ci mostra la definizione di una funzione con parametri e con un valore di ritorno anticipato dal suo tipo (quel "int" prima della parentesi graffa).
I parametri in questo esempio sono gestiti per valore, ovvero è il loro valore che è passato non c'è alcuna modifica ai parametri stessi. Go tuttavia permette anche il passaggio per riferimento attraverso l'operatore &. Così facendo il parametro viene manipolato nella sua allocazione originale. In questo caso bisogna però ricordare che lavoriamo tramite puntatori in quanto passiamo degli indirizzi di memoria in pratica, per cui è necessario entrare in quest'ottica. L'esempio che segue, e che sarà più chiaro quando altri concetti saranno introdotti, presenta un'applicazione pratica di quanto accennato:

  esempio 4.3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main
import "fmt"
func main() {
var num1 int
fmt.Print("Inserisci un numero: ")
fmt.Scanln(&num1)
cambia(&num1)
fmt.Print(num1)
}

func cambia(p *int) *int {
*p = 9
return p
}

In pratica alla riga 7 passiamo come parametro la locazione di memoria in cui si trova num1 col suo valore iniziale definito da noi in fase di input (non dategli 9 come valore :-)). Alla riga 11 specifichiamo che il tipo di parametro è qualcosa che punta ad un intero (poichè num1 è definito tale alla riga 4) e la funzione dovrà restituire pure un puntatore ad un intero. La riga 12 inserisce il valore 9 nella locazione (non va bene scrivere p = 9 perchè p è un puntatore, quindi dobbiamo "entrare" nell'area puntata). La riga 8 espone il nuovo valore memorizzato, ovvero 9. Per ora non è importante capire tutto. Basti sapere per ora che questa è una soluzione più economica e veloce rispetto a fare delle copie delle variabili (che è comunque spesso una soluzione accettabile).
Come abbiamo detto è possibile avere più valori di ritorno da una funzione:

  esempio 4.4
1
2
3
4
5
6
7
8
9
10
11
12
package main
import "fmt"
func main() {
var num1 int = 0
var num2 int = 0
num1, num2 = valori()
fmt.Print(num1 + num2)
}

func valori() (int, int) {
return 4, 6
}

L'esempio è banalissimo e quanto di più scheletrico possibile per poterci focalizzare sui punti chiave. Come si vede l'attribuzione dei parametri è regolata in forma posizionale mentre è obbligatorio mettere la lista dei valori di ritorno all'interno delle parentesi tonde (riga 10) rispettando l'ordine giusto dei tipi da attribuire alle variabili, la sequenza delle quali nell'esempio è alla riga 6. Go non si limita però a questo ed è possibile attribuire un nome ai valori che saranno restituiti di nome che si possa lavorare direttamente su di essi. Ad esempio è possibile, nell'esempio 4.4 riscrivere la funzione "valori" come segue:

func valori() (x int, y int) {
x = 2 + 3
y = 4 + 5
return x, y

Sempre a proposito della possibilità di offrire valori di ritorno multipli esiste anche un modo pratico per ignorare un valore che in una certa occasione non serva. E' sufficiente abbinare la valore di cui non si vuole tenere conto l'identificatore blank _ (underscore) che scarta il valore che gli viene dato.

Altra caratteristica interessante è quella di poter usare le funzioni in luogo dei propri parametri Lo schema è il seguente:

f1() int
f2(int) int -> f2(f1()) int


Premettendo che la cosa si può generalizzare per n parametri abbiamo che: la funzione f2 ha bisogno di un intero come parametro in ingresso - la funzione f1 restituisce un intero -> la funzione f1 può essere usata come argomento in ingresso per f2. (nota: non ho trovato molto semplice usare questa feature).

Una funzione peraltro non è innestabile in un'altra, quindi non è possibile dichiarare una funzione all'interno di un'altra anche se si possono usare funzioni anonime.

In Go non è permesso l'overload delle funzioni ovvero non si possono dichiarare più funzioni con lo stesso nome ma con una lista di parametri diversi o con un valore di ritorno diverso. Questo è dovuto a considerazioni sulle performance. Quindi funzioni diverse devono avere nomi diversi (che poi non è un grande sforzo, in genere)

Per ora ci fermiamo qui e contnuueremo a parlare di funzioni nel prossimo paragrafo.