Archivi Categorie: Go

Il linguaggio di programmazione Go, da Google.

Go Fibonacci!

Sommario

Creeremo un programma per la generazione dei numeri di Fibonacci per poi entrare nel campo dell’esecuzione concorrente in Go.

Fibonacci

La sequenza di Fibonacci si genera sommando i precendenti due numeri della serie. Questa regola necessita di definire i primi due numeri e questi sono semplicemente assunti pari a 0 ed 1.
In Go il calcolo dell’ennesimo numero della sequenza può essere ottenuto con il codice seguente sfruttando direttamente la definizione della serie e l’assegnazione multipla (linea 8) tra l’altro disponibile anche nei linguaggi Lua e Python:

package main

// trova l'ennesimo numero della serie
// di Fibonacci
func fibonacci(n int) int {
    var a, b int = 0, 1
    for i := 0; i < n-1; i++ {
        a, b = b, a+b
    }
    return a
}

func main() {
    for i := 1; i < 10; i++ {
        print(fibonacci(i), " ")
    }
    println()
}

Calcoli indipendenti

Il supporto alla programmazione concorrente del Go è probabilmente — in un mondo multiprocessore — la principale caratteristica per la sua diffusione, e pensare che nel linguaggio vi sono pochissimi costrutti sintattici per implementarla (caso mai la semplicità fosse un vantaggio).
Li abbiamo visti già tutti all’opera su Ok, panico!, grazie ai post di Juhan ;-).

Come forse avrete intuito, proveremo a calcolare molti numeri di Fibonacci in modo indipendente. Questa parola è importante perché la programmazione concorrente non è altro che un insieme di esecuzioni che si svolgono indipendentemente una dall’altra — come sottolinea Robert Pike. La distinzione è dovuta al fatto che ci si può sbagliare usando per questa modalità di esecuzione il termine parallelismo, che invece indica un insieme di esecuzioni che avvengono contemporamente. Nei moderni pc multicore, l’esecuzione concorrente può avvicinarsi al parallelismo.

Fibonacci independente

Un semplice schema per l’esecuzione concorrente di più funzioni di Fibonacci, è quello di avviarne l’esecuzione in una goroutine ed attenderne in quella principale i risultati provenienti da un canale.

Ecco il codice in cui si deve intendere che la funzione mancante fibonacci() sia quella del listato precedente:

func fibonacci(n int) int64 {
    // as before with int64 return value
}

var num = []int{50, 36, 80, 93, 66}

func main() {
    ans := make(chan int, len(num)) // buffered channel
    for _, f := range num {
        go func(f int) {
            ans <- fibonacci(f)
        }(f)
    }
    
    // stampo i risultati provenienti dal canale
    for i := 0; i < len(num); i++ {
        fib := <-ans
        fmt.Printf("Fibonacci(%d)=%d\n", num[i], fib)
    }
}

Nella funzione main() dopo aver creato un canale, avviamo tante goroutine quanti sono i numeri della serie da calcolare iterando sugli elementi di uno slice (num). Al termine del ciclo avremo nel nostro caso 5 goroutine in esecuzione indipendente da quella principale.
La prima diversità dalla programmazione classica è che l’istruzione go avvia una nuova linea di esecuzione senza attendere che questa termini. Quasi immediatamente raggiungiamo quindi il secondo ciclo for che preleva in sequenza i dati dal canale.

Questo schema è piuttosto semplice. Non conosciamo l’ordine con cui i dati arrivano e dobbiamo ricordarci che l’istruzione

        fib := <-ans

comporta il blocco dell’esecuzione della goroutine principale (quella in cui gira la funzione main()), che attende fino all’arrivo di un dato, assicurandoci che vengano attesi cinque valori dal canale altrimenti la funzione main() terminerà prima che le goroutine di calcolo abbiano portato a termine il lavoro.
La goroutine infatti non sanno niente di quello che stanno facendo le altre eventuali goroutine in esecuzione e se main() termina, termineranno forzatamente tutte.
Dal punto di vista della singola goroutine al termine del calcolo l’invio sul canale del risultato è immediato, essendo questo dotato di capacità pari al numero dei risultati che vi saranno inviati (buffered channel), altrimenti essa avrebbe dovuto attendere che la gorountine main() fosse pronta a ricevere un dato (sincronizzazione del mandante con il ricevente).

Per capire la concorrenza in Go conviene quindi immaginare il funzionamento delle cose in modo dinamico tenendo conto del blocco o meno dell’invio o della ricezione dei dati dai canali.

Prestazioni

Ho fatto alcune prove variando la quantità dei numeri da calcolare. Sulla mia macchina Linux dotata di un processore con un unico core, le prestazioni migliorano solo di alcuni punti percentuali, ed addirittura peggiorano quando crescono i numeri da calcolare in num.
Evidentemente il costo per la creazione delle goroutine — sia pure piccolo — non è compensato su una macchina ad un unico core da vantaggi particolari.
Oltre a capire sperimentando il codice, quello che importa adesso non sono le prestazioni ma che occorre considerare con precisione la natura del problema per poter scegliere o meno una soluzione a calcolo indipentente.

Si tratta di un argomento affascinante!

Difetto

Il codice precedente ha un difetto: è necessario attendere che tutte le goroutine siano state create e lanciate prima di passare a raccogliere i risultati. Per esempio se per creare una goroutine accorresse 1 millisecondo e ciascuna mediamente richiedesse 50ms di esecuzione concorrente, allora i risultati dovrebbero attendere stipati nel canale se le goroutine fossero circa più di 50.

La soluzione è quella di inserire il ciclo di creazione delle goroutine esso stesso all’interno di una goroutine:

func main() {
    // Use all the machine's cores
    runtime.GOMAXPROCS(runtime.NumCPU()) 
    res := make(chan int64)
    
    go func() {
        for _, f := range num {
            go func(f int) {
                res <- fibonacci(f)
            }(f)
        }
    }()
    
    for i := 0; i < len(num); i++ {
        <-res
    }
}

Altro simpatico esempio elegante

Questa volta spediamo sul canale la serie di Fibonacci da una goroutine separata basata su un ciclo for infinito (a terminare il programma sarà brutalmente il termine della funzione main() nella quale chiederemo la stampa dei primi dieci numeri della serie):

package main

import "fmt"

func main() {
    ch := make(chan int)
    go func(a1, a2 int) {
        for {
            ch <- a1
            a1, a2 = a2, a1 + a2
        }
    }(0, 1)
    
    for i := 0; i<10; i++ {
        fmt.Print(<- ch, " ")
    }
    fmt.Println()
}

Sfida…

Invito i visitatori del blog a presentare nuovi schemi di calcolo concorrente o semplicemente solo i risultati ottenuti con i vostri megacalcolatori.

Quello che serve è una installazione di Go, e magari sapere che esistono comode funzioni nel pacchetto time che misurano con precisione il tempo macchina, come nel seguente esempio:

package main

import (
    "fmt"
    "math"
    "time"
)

func main() {
    multiPi := make([]float64, 10000)
    t := time.Now()
    for i := 0; i < 10000; i++ {
        multiPi[i] = math.Pi * float64(i)
    }
    fmt.Printf("Executin time: %v\n", time.Since(t))
}

Un saluto.
R.

La varietà di variabili in Go

Scarica l’articolo per la stampa nel formato pdf: golang_tipi_variabili1.pdf

Sommario

Faremo un excursus sul significato delle variabili chiarendone il loro funzionamento nei moderni linguaggi di programmazione, in particolare con riferimento al Go. Ci soffermeremo sul concetto di variabile valore e su quello di variabile riferimento.
Per la comprensione e l’esecuzione degli esperimenti proposti, è necessario solo un po’ di concentrazione e la lettura dei post sul linguaggio Go apparsi su questo stesso blog.

Variabili

Tutti o quasi tutti i linguaggi di programmazione comprendono tra i concetti di base quello di variabile. Il nome stesso ci dice che è un qualcosa che può cambiare durante l’esecuzione del programma e quel qualcosa è il dato che la variabile rappresenta.

Per fare un esempio di codice in un linguaggio generico, possiamo in modo molto naturale assegnare il valore numerico 10 alla variabile v e sottrarre poi 5:

-- in un linguaggio generico...
v = 10
print( v )  --> stampa 10

v = v - 5
print( v )  --> stampa 5

Se provo a creare una seconda variabile q con il valore della prima mi aspetto che riceva il valore che v conteneva al momento dell’assegnazione. Ma se poi la prima variabile cambia che ne è della seconda?
Il codice seguente chiarisce il dubbio sul comportamento delle variabili:

-- sempre scrivendo il codice
-- in un linguaggio generico
v = 10
q = v

print(q) --> stampa 10

-- poi v cambia...
v = v - 5
print(q) --> stampa 10 o 5?

Ci si può attendere due soli risultati:

  1. la seconda variabile non cambia mantenendo il valore 10;
  2. la seconda variabile cambia cambiando a sua volta in 5 come la prima.

Dal C, al C++, a Java

Siamo partiti da un concetto piuttosto semplice: dare un nome al contenitore di un dato che può cambiare durante l’esecuzione del programma — ovvero a “run-time” — ma ben presto ci siamo resi conto che è necessario scegliere come effettivamente rappresentare le informazioni, e queste scelte influenzeranno nel bene e nel male le applicazioni.

Ogni linguaggio dunque affronta problemi di progettazione complessi, con esigenze spesso opposte, come ben testimonia la loro evoluzione storica, per esempio dal C, al C++, a Java. Ad ogni generazione l’ingegneria del software mette a frutto anni di lavoro e l’esperienza di milioni di righe di codice in un nuovo linguaggio.

Tipi di variabili

Eravamo rimasti al dubbio se nel nostro ipotetico linguaggio sia opportuno o meno agganciare il valore della seconda variabile a quello della variabile da cui era stata costruita. Verifichiamo subito qual è stata la scelta dei progettisti del Go 🙂

Per una variabile di tipo intero la verifica è:

package main

import "fmt"

func main() {
    v := 10
    q := v
    // modo compatto di scrivere v = v - 5
    v -= 5

    // stampa 10 o stampa 5?
    fmt.Println(q)
}

invece per una variabile di tipo []int, ovvero uno slice la verifica è:

package main

import "fmt"

func main() {
    // uno slice:
    v := []int{10}
    q := v
    v[0] -= 5
    // stampa [10] o stampa [5]?
    fmt.Println(q)
}

Otteniamo tutti e due i comportamenti! Verificate per esercizio a quale dei due esempi corrispondono i casi (non vi farà male e vi ruberà solo un minuto se utilizzate Go Playground).

Variabili valore

Se la variabile è intesa come il contenitore stessso in cui si trova il dato, creandone una per mezzo di un’assegnazione di un’altra variabile verrà creato semplicemente un nuovo contenitore con il dato in quel momento contenuto in quest’ultima.
Stiamo parlando delle variabili valore che forniscono l’accesso diretto al dato.

Quello che abbiamo definito contenitore è in sostanza il segmento di memoria che contiene la rappresentazione binaria del dato. Nella figura di seguito è rappresenta la dinamica della creazione e della modifica delle variabili valore v e q del codice di esempio.

Schema di funzionamento delle variabili valore

Schema di funzionamento delle variabili valore

…e le strutture?

Questo ve lo posso dire: le strutture in Go sono memorizzate in variabili valore, lascio a voi scrivere per utile esercizio il breve codice che lo verifica…

Variabili riferimento

Se la variabile è intesa come il nome del contenitore in cui si trova il dato, allora creandone una per mezzo di un’assegnazione da un’altra variabile, verrà copiato il nome del contenitore nella nuova. Si tratta delle variabili riferimento che forniscono un accesso indiretto al dato tramite informazioni riguardanti il contenitore.

Anche le variabili riferimento sono di tipo valore nel senso che dopotutto contengono direttamente le informazioni solo che non rappresentano il dato ma solo quello che serve al compilatore per raggiungerlo.

Ecco una rappresentazione schematica dello stesso codice precedente che coinvolge le variabili v e q ma stavolta di classe reference.

Schema di funzionamento delle variabili riferimento

Schema di funzionamento delle variabili riferimento

Il terzo tipo: i puntatori

Non dovrebbe esistere un terzo tipo di variabili perché all’inizio del post abbiamo riscontrato che ci possono essere solo due situazioni che poi abbiamo associato alle variabili valore ed alle variabili riferimento.

In Go sono disponibili, come in C ed in C++, i puntatori ed in effetti non costituiscono un terzo tipo di variabile perché (almeno in Go) sono contenuti in normali variabili valore. Per convincerci eseguiamo il solito programma di prova:

package main

import "fmt"

func main() {
    n, m := 10, 5 // tipi int

    p1 := &n // tipo *int
    p2 := p1

    p1 = &m
    fmt.Println(*p1) // stampa 5
    fmt.Println(*p2) // stampa 10
}

I puntatori quindi sono variabili valore che contengono l’indirizzo grezzo di memoria dove si trova un dato. Dal punto di vista del linguaggio possiamo considerare i puntatori come delle variabili riferimento primitive. Con i puntatori infatti, gestiamo esplicitamente indirizzi di memoria ottenendoli con l’operatore & come abbiamo appena visto nel listato precedente, ed indichiamo esplicitamente il valore puntato deferenziando il puntatore con l’operatore *.

Se affermiamo questo, possiamo anche dire in modo speculare che le variabili riferimento sono un tipo evoluto di puntatore perché nel codice le scriviamo come normali variabili. È il compilatore che svolge il lavoro “primitivo” per noi elaborando nel modo opportuno le informazioni effettive celate nella variabile riferimento.

Possiamo a questo punto rispondere a queste domande:

  1. due variabili riferimento di uno stesso oggetto avranno lo stesso indirizzo di memoria?
  2. sapendo che in Go alle funzioni vengono passate le copie degli argomenti, cosa accade se modifichiamo una variabile riferimento all’interno di una funzione?
  3. in Go, conviene passare ad una funzione un puntatore a slice o lo slice stesso?

Risposta uno

Due variabili riferimento di uno stesso oggetto avranno lo stesso indirizzo di memoria?

Se una variabile riferimento è veramente una variabile valore che contiene un riferimento ad un dato in memoria, ne segue che avrà un proprio indirizzo di memoria diverso da quello di qualsiasi altra variabile dello stesso tipo anche se si riferisce allo stesso oggetto.
Verifichiamo per prima cosa se possiamo conoscere facilmente l’indirizzo di memoria di una variabile valore e di una variabile riferimento con questo minicodice:

package main

import "fmt"

func main(){
    val := 10
    // stampa --> 'val' address: 0x10d50038
    fmt.Println("'val' address:", &val)

    ref := []int{1, 2, 3}
    // stampa --> 'ref' address: &[1 2 3]
    fmt.Println("'ref' address:", &ref)
}

L’operatore & restituisce il puntatore con l’indirizzo di memoria di una variabile qualsiasi, ma la funzione fmt.Println() esegue giustamente una stampa ad alto livello del puntatore alla variabile riferimento rispettandone la natura.
Anziché far decidere alla funzione tuttofare fmt.Println() il formato di stampa possiamo farlo esplicitamente usando il segnaposto %p (consulta la documentazione del pacchetto fmt):

package main

import "fmt"

func main() {
    // una slice, due variabili reference
    s1 := []int{1, 2, 3, 4 ,5}
    s2 := s1

    fmt.Printf("address di s1: %p\n", &s1)
    fmt.Printf("address di s2: %p\n", &s2)
    // stampano -->
    // address di s1: 0x10d6f100
    // address di s2: 0x10d6f0f0

    // verifichiamo che le due variabili
    // si riferiscono allo stesso slice
    s1[0]++
    s1[1]++
    s1[2]--
    s2[2]--
    s2[3]++
    s2[4]++
    fmt.Println(s1,s2)
    // stampa --> [2 3 1 5 6] [2 3 1 5 6]
}

La risposta iniziale è corretta: due variabili riferimento non sono puntatori non contenendo un riferimento diretto alla memoria ma contengono dati nascosti al programmatore ciascuna in un’area di memoria propria.

Risposta due

Sapendo che in Go alle funzioni vengono passate le copie degli argomenti, cosa accade se modifichiamo una variabile riferimento all’interno di una funzione?

Dunque, se l’argomento viene copiato all’interno della funzione la variabile riferimento sarà si una copia dell’originale, e conterrà quindi le stesse informazioni nascoste per l’accesso ai dati, ma un istruzione di modifica all’interno della funzione modificherà l’oggetto, l’unico oggetto, a cui sia l’argomento passato che l’argomento di funzione si riferiscono.

Utilizzando il tipo map[string]int lascio al lettore la scrittura del minicodice di verifica…

Risposta tre

In Go, conviene passare ad una funzione un puntatore a slice o lo slice stesso?

La variabile che contiene uno slice è reference, quindi passare un puntatore a slice è solo più complicato: non ci si guadagna nemmeno con la dimensione in memoria degli argomenti.

Una domanda per il lettore curioso

In Go le funzioni sono tipi di prima classe (come in Lua, copioni!). Questo significa che possiamo passare una funzione come argomento di un’altra e creare funzioni anonime.
La domanda è questa:

Le variabili che contengono una funzione in Go sono di tipo valore o di tipo riferimento?

Conclusione

Abbiamo studiato insieme le due classi di variabili in linguaggio Go, variabili valore e variabili riferimento, lasciando al lettore un po’ di utile lavoro da fare su alcuni esercizi.

Come regola generale i moderni linguaggi di programmazione fanno si che ai tipi di dato semplici come i numeri ed i booleani sia associato il tipo di variabile valore, mentre ai dati complessi come gli oggetti sia associato il tipo di variabile riferimento.

Un saluto.
R.

Go-giocare con le mappe

Sommario

Utili esercizi in linguaggio Go con le mappe.

Come fareste a…

…fare il conteggio di quanti oggetti ci sono sulla scrivania?
Per prima cosa dovremo pensare ad una rappresentazione dei dati. Decidiamo per la più semplice, ovvero per un elenco di parole: quando troviamo un oggetto ne riportiamo il nome nell’elenco.

In Go la cosa più naturale per rappresentare un elenco di parole è utilizzare il tipo slice: si tratta di una sorta di finestra su un array, che in Go sono tipi in cui la lunghezza non solo è non modificabile ma fa parte del tipo stesso.

Quello che noto è che i progettisti del linguaggio fanno di tutto per associare i dati ad un tipo, tipizzazione forte, e su questa base vogliono mettere a disposizione strutture agili ed efficienti. Questo significa proporre un linguaggio con la robustezza del C++ ma con la facilità di Python, ed è solo uno degli aspetti del carattere equilibrato del Go…

// creazione di uno slice di stringhe con la
// sintassi letterale: esprimo direttamente i valori
var things = []string{"BOOK","CALENDAR","MANUAL","ECC"}

Stampiamo subito l’elenco!

Per stampare l’elenco è sufficiente iterare sullo slice. Ecco il programma completo:

package main

import "fmt"

var things = []string{"BOOK",
    "CALENDAR",
    "MANUAL",
    "BOOK",
    "ECC"}

func main(){
    for _, t := range things {
        fmt.Println(t)
    }
}

Le mappe

Per contare gli oggetti possiamo usare una mappa, ovvero un dizionario di chiavi/valori già disponibile nel linguaggio. Le chiavi saranno i nomi degli oggetti ed i valori associati saranno le quantità. Creiamo subito una mappa del genere che dobbiamo inizializzare con make():

// a new map...
var inv map[string]int
inv = make(map[string]int)

// or in one declaration line
var inv = make(map[string]int)

Al solito, siamo obbligati a definire con esattezza sia il tipo della chiave che quello del valore, ma il fatto che le mappe sono oggetti interni al linguaggio Go, ovvero come si dice più sinteticamente built-in, fa pensare che si sia voluto facilitarne l’uso e, con la filosofia dell’equilibrio del robusto fa semplice e divertente, possiamo crearla sia per via letterale che usando la dichiarazione breve:

// empty literal map definition
var inv = map[string]int{}

// short declaration syntax
inv := map[string]int{}

// or
inv := make(map[string]int)

Il procedimento per elaboare l’elenco è questo: per ogni nome di oggetto creiamo una chiave a cui assegnamo il valore 1, se la chiave esiste già, prendiamo il valore attuale e lo incrementiamo di 1. Ecco il programma d’esempio completo:

package main

import "fmt"

var things = []string{"BOOK","CALENDAR","MANUAL","BOOK","ECC"}

func main(){
    inv := make(map[string]int)
    for _, t := range things {
        _, found := inv[t]
        if !found { // ! is the NOT logic operator
            inv[t] = 1
        } else {
            inv[t] = inv[t] + 1
        }
    }
    fmt.Println(inv)
}

Oltre alla sintassi per accedere ai campi del dizionario, di nuovo c’é l’espressione alla riga 10: inv[t] restituisce il valore associato alla chiave t ed il valore booleano vero o falso a seconda che la chiave risulti o meno già definita.
Poiché non sono ammesse variabili istanziate ma poi non utilizzate, occorre inserire il blanck identifier ovvero il trattino basso al nome della variabile che restituisce il valore, ovvero la prima.

Notiamo anche che la funzione Println() del pacchetto fmt stampa qualsiasi cosa gli sia data in pasto, numeri, slice, e mappe in questo caso.

Sempre più idiomatic Go

Le prime modifiche al listato precedente sono l’assegnazione del flag found all’interno dell’if così che sia definito solo quando serve, e l’uso dell’operatore di incremento:

package main

import "fmt"

var things = []string{"BOOK","CALENDAR","MANUAL","BOOK","ECC"}

func main(){
    inv := make(map[string]int)
    for _, t := range things {
        if _, found := inv[t]; !found {
            inv[t] = 1
        } else {
            inv[t] += 1
        }
    }
    fmt.Println(inv)
}

Già più compatto ed idiomaticoso, ma possiamo fare ancora meglio:

package main

import "fmt"

// "pretty printing" of an inventory
func printInventory(data map[string]int) {
    for thing, num := range data {
        fmt.Println(num, thing)
    }
}

func main() {
    inv := make(map[string]int)
    for _, t := range things {
        inv[t]++
    }

    printInventory(inv)
}

// a new slice of things that I founded on my desk
var things = []string{
    "PENCIL", "BOOK", "BAG", "BOOK", "BOOK", "BOOK",
    "BAG", "NEWSPAPER", "MANUAL", "CALENDAR","BAG",
    "CANDLE", "BOTTLE", "PENCIL", "PAPER","GLASSES",
    "NEWSPAPER", "PAPER","MANUAL","PHOTOGRAPH","MUG",
}

/*
$ go run ex2.go
4 BOOK
1 CANDLE
3 BAG
1 BOTTLE
1 PHOTOGRAPH
2 PAPER
1 GLASSES
1 CALENDAR
2 PENCIL
1 MUG
2 MANUAL
2 NEWSPAPER
$ Exit code: 0
*/

Ho tolto l’if e continua a funzionare!
In altri linguaggi come per esempio Lua, non possiamo assegnare un valore ad una chiave che non esiste ancora, mentre in Go si. In Lua le variabili non vengono inizializzate se non nel momento dell’assegnazione di un valore. Il tipo viene determinato all’istante dal compilatore solo quando il valore è determinato. In Go è il contrario: il compilatore conosce già il tipo ed inizializza le variabili al corrispondente valore nullo.

Ora, anche in Go a favore della filosofia del robusto ma intuitivo, è possibile creare variabili con la determinazione del tipo fatta dal compilatore, ma quando viene creato un elemento della mappa il valore della chiave è inizializzato al valore nullo che per gli interi è zero, così posso immediatamente incrementarlo.

Nel codice ho aggiunto una nuova funzione che stampa l’inventario in colonna. Notare che si è liberi di inserire il codice nell’ordine che si preferisce: nell’esempio la funzione di stampa compare prima che venga utilizzata in main(), mentre succede il contrario per la definizione globale dello slice things.
L’output del programma è riportato alla fine del listato all’interno di un commento multiriga (in Go i commenti sono identici a quelli del C++ ed anche questa somiglianza non è un caso…).

Go-nsigli

Per imparare un linguaggio è necessario scrivere programmi di esercizio. Va bene seguire un buon libro, ma è indispensabile ripetere gli esempi ridigitandoli o addirittura riscrivendoli con carta e matita. Solo così si comincia a pensare nel nuovo linguaggio.
Non è necessario installare nulla, basta recarsi sul sito Playground per essere pronti a digitare ed eseguire il codice.

Ripassate anche gli altri post tematici di Juhan sul Go apparsi sul blog, specie quelli sugli slice e sulle mappe

A presto…
R.

Go – conclusione

TestGopherCon questo post finisce l’esame del linguaggio Go. La mia guida, Caleb Doxsey racconta ancora alcune cose, riguardanti il Web che ometto: io sono ancora in locale 😉

Un’altra cosa trattata sono i dati passati sulla linea di comando, ecco un paio di esempi, banali.

package main

import ("fmt";"flag";"math/rand")

func main() {
    // Define flags
    maxp := flag.Int("max", 42, "the max value")
    // Parse
    flag.Parse()
    // Generate a number between 0 and max
    fmt.Println(rand.Intn(*maxp))
}

args

Per qualche motivo che non ho appurato il “numero casuale” è sempre lo stesso; credo occorra fornire un seed, basato sul tempo ma non ho provato.

package main

import ("fmt";
        "os";
        "strconv"
)

func main() {
    v := os.Args
    lun := len(v)
    fmt.Println(lun, "\n", v)
    n, _ := strconv.Atoi(v[2])
    fmt.Println(n, n/6)
}

args2

Niente di nuovo tranne che se ci si dimentica (o non si sa) come usare una funzione c’è Effective Go.

A questo punto occorrerebbe installare il compilatore, quello serio che produce gli eseguibili, il GCC ‘nsomma. Ma tra poche settimane esce la nuova versione di Ubuntu e formatto il disco…

Inoltre è da vedere se si userà Go per qualche progetto. È molto più semplice del C++ ma molto meno performante (secondo prove fatte da miei contatti). Inoltre per le cose piccole io continuo a pensare che la soluzione migliore sia Python.

Se ci si limita a Windows poi ci sono soluzioni non-free che piacciono parecchio (Delphi e Visual Basic). Resta da vedere il loro destino con la nuova versione (non l’ho ancora vista).

In ogni caso per quel, che ho visto, la scelta degli strumenti da utilizzare dipende da tanti fattori, non sempre chiari e condivisibili. C’è poi la necessità di non dover conoscere millemila cose. Anche se sto iniziando l’esame di Ruby, nuovo per me. Ma è un gioco 😉

Go – container e sort

womens-gopherOltre alle liste e alle mappe Go ha molte altre collezioni nel package container. Per esempio diamo uno sguardo a container/list.

Ecco la doubly-linked list, un tipo di struttura come in figura

dll

Ogni nodo contiene un valore (nel nostro caso 1, 2, 3) e un puntatore al nodo successivo. Essendo una lista linkata doppia ogni nodo ha altresì un puntatore al nodo precedente. Questa può essere creata in questo modo:

package main

import (
    "fmt"
    "container/list"
)

func main() {
    var x list.List
    x.PushBack(1)
    x.PushBack(2)
    x.PushBack(3)
    for e := x.Front(); e != nil; e=e.Next() {
        fmt.Println(e.Value.(int))
    }
}

l1

Una lista con zero valori è una lista vuota, *List può anche essere creata con list.New. I valori sono aggiunti con PushBack. Nel nostro caso il ciclo li percorre fino a trovare nil.

Il package sort contiene diverse funzioni per l’ordinamento di dati arbitrari. Eccone un esempio:

package main

import ("fmt" ; "sort")

type Person struct {
    Name string
    Age int
}

type ByName []Person

func (this ByName) Len() int {
    return len(this)
}

func (this ByName) Less(i, j int) bool {
    return this[i].Name < this[j].Name
}

func (this ByName) Swap(i, j int) {
    this[i], this[j] = this[j], this[i]
}

func main() {
    kids := []Person{
        {"Jill",9},
        {"Jack",10},
    }
    sort.Sort(ByName(kids))
    fmt.Println(kids)
}

s1

La funzione Sort nel package sort prende una sort.Interface e la ordina. la sort.Interface richiede 3 metodi: Len, Less e Swap. Abbiamo creato un nuovo tipo (ByName) e una slice di quello che vogliamo ordinare. L’ordinamento consiste quindi solo nell’inserire la lista dentro al nostro tipo. Per ordinare la lista in base all’età si procede così:

package main

import ("fmt" ; "sort")

type Person struct {
    Name string
    Age int
}

type ByName []Person

type ByAge []Person

func (this ByAge) Len() int {
    return len(this)
}

func (this ByAge) Less(i, j int) bool {
    return this[i].Age < this[j].Age
}

func (this ByAge) Swap(i, j int) {
    this[i], this[j] = this[j], this[i]
}

func main() {
    kids := []Person{
        {"Jill",9},
        {"Jack",10},
    }
    sort.Sort(ByAge(kids))
    fmt.Println(kids)
}

s2

OK? 😉 😮 😀

Go, input / output

photo_015

Oggi il package io, contiene poche funzioni ma ci sono le interfacce per altri packages, i più importanti dei quali sono Reader e Writer che supportano la lettura con Read e la scrittura con Write.
Parecchie funzioni usano Reader o Writer come argomenti, per esempio Copy:

func Copy(dst Writer, src Reader) (written int64, err error)

Per leggere o scrivere un []byte o una string si può usare Buffer del package bytes:

var buf bytes.Buffer
buf.Write([]byte("test"))

Un Buffer non necessita di essere inizializzato e supporta sia le interfacce a Reader che a Writer.
Se si deve solo leggere da una stringa si può usare strings.Newreader, funzione più efficiente rispetto all’uso di un buffer.

Files e Folders

Caleb usa folders, io le chiamerei directories.
Per aprire un file usiamo Open, contenuta in os. Ecco come leggere un file e visualizzare il suo contenuto sul terminale:

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("test.txt")
    if err != nil {
        // handle the error here
        return
    }
    defer file.Close()
    // get the file size
    stat, err := file.Stat()
    if err != nil {
        return
    }
    // read the file
    bs := make([]byte, stat.Size())
    _, err = file.Read(bs)
    if err != nil {
        return
    }
    str := string(bs)
    fmt.Println(str)
}

lp

Notare defer file.Close() per chiuderlo appena si esce dalla funzione (e non dimenticarlo aperto).

Leggere files è talmente comune che c’è un modo più spiccio:

package main

import (
    "fmt"
    "io/ioutil"
)

func main() {
    bs, err := ioutil.ReadFile("test.txt")
    if err != nil {
        return
    }
    str := string(bs)
    fmt.Println(str)
}

rf

Ecco come creare un file:

package main

import (
    "os"
)

func main() {
    file, err := os.Create("cr-test.txt")
    if err != nil {
        // handle the error here
        return
    }
    defer file.Close()

file.WriteString("test\n")
}

cr

Per leggere il contenuto di una directory usiamo la stessa os.Open passandogli il path della stessa invece del nome del file e poi usiamo Readdir:

package main

import (
    "fmt"
    "os"
)

func main() {
    dir, err := os.Open(".")
    if err != nil {
        return
    }
    defer dir.Close()
    fileInfos, err := dir.Readdir(-1)
    if err != nil {
        return
    }
    for _, fi := range fileInfos {
        fmt.Println(fi.Name())
    }
}

ld

Spesso occorre percorrere ricorsivamente una path (leggere il contenuto della dir, poi quello delle sue sub-dir e poi quello delle sub-sub-dir …). C’è per questo la funzione Walk nel package path/filepath:

package main

import (
    "fmt"
    "os"
    "path/filepath"
)

func main() {
    filepath.Walk(".", func(path string,
                info os.FileInfo, err error) error {
        fmt.Println(path)
        return nil
    })
}

wd

La funzione passata a Walk è chiamata per ogni file e folder nella directory passata come root, “.” in questo caso.

Errori

Come abbiamo già visto c’è un tipo built-in per gli errori: error. Possiamo creare i nostri errori usando New nel package errors:

package main

import (
    "errors"
    "fmt"
)

func main() {
    err := errors.New(
        "il mio messaggio\ndi errore personalizzato")
    // ...
    if err != nil {
        fmt.Println(err)
    }
}

err

Alla prossima 😉

Go – i core packages I – strings

pDai, da sempre si usano le librerie, raccolta di funzioni. Qui si chiamano packages. Vediamo i principali, uno per post, oggi il primo.

Un gran numnero di funzioni sulle stringhe sono incluse nel package strings:

package main

import (
    "fmt"
    "strings"
)

func main() {
    const res = "->"
    s := "strings.Contains(\"test\", \"es\")"
    fmt.Println(s, res,
        strings.Contains("test", "es"))

    s = "strings.Count(\"test\", \"t\")"
    fmt.Println(s, res,
        strings.Count("test", "t"))

    s = "strings.HasPrefix(\"test\", \"te\")"
    fmt.Println(s, res,
        strings.HasPrefix("test", "te"))

    s = "strings.HasSuffix(\"test\", \"st\")"
    fmt.Println(s, res,
        strings.HasSuffix("test", "st"))

    s = "strings.Index(\"test\", \"e\")"
    fmt.Println(s, res,
        strings.Index("test", "e"))

    s = "strings.Join([]string{\"a\",\"b\"}, \"-\")"
    fmt.Println(s, res,
        strings.Join([]string{"a","b"}, "-"))

    s = "strings.Repeat(\"a\", 5)"
    fmt.Println(s, res,
        strings.Repeat("a", 5))

    s = "strings.Replace(\"aaaa\", \"a\", \"b\", 2)"
    fmt.Println(s, res,
        strings.Replace("aaaa", "a", "b", 2))

    s = "strings.Split(\"a-b-c-d-e\", \"-\")"
    fmt.Println(s, res,
        strings.Split("a-b-c-d-e", "-"))

    s = "strings.ToLower(\"TEST\")"
    fmt.Println(s, res,
        strings.ToLower("TEST"))

    s = "strings.ToUpper(\"test\")"
    fmt.Println(s, res,
        strings.ToUpper("test"))
}

p0

Notare che Split si comporta diversamente da quanto enunciato da Caleb.

Alle volte serve convertire la stringa un un array di byte e viceversa. Ecco come:

package main

import ("fmt")

func main() {
    arr := []byte("test")
    fmt.Println(arr)

    str := string([]byte{'t','e','s','t'})
    fmt.Println(str)

    a2 := []byte{116, 101, 115, 116}
    fmt.Println(a2)
    fmt.Println(string(a2))
}

p1

Ahemmm… di nuovo, ma OK :-; 🙂 😀

Go – testing

pgoProgramming is not easy” dice Caleb. Ah! ma allora non è solo una mia impressione; l’altro giorno avevo una funzione di una decina di righe che non funzionava ed ero continuamente disturbato da mille cose diverse, panico!

Poi dall’amico Bit3Lux è arrivata una mail. Ho risposto staccando dal bug. La pausa è stata molto salutare, subito dopo ho visto che avevo scritto a = t invece di t = a. Problema risolto.
Go però è fatto da gente che la sa lunga, e sa quello che capita e che non sempre interviene Bit3Lux. E allora ecco un programma che rende facile testare il codice scritto.

Non riporto l’esempio di Caleb, troppo specifico. C’è anche un altro motivo: con il tempo ho imparato, a mie spese, che conviene testare ogni pezzetto di codice che si scrive appena possibile per cui quello che fa il comando go test lo faccio abitualmente, in modo artigianale, certo. Comunque potrei ricredermi, rimando la cosa al primo caso grosso e incasinato (si può dire incasinato, vero?).

È la prima volta che mi stacco da questa guida, ma sono abbastanza convinto di essere nel giusto. D’altronde sono vecchio, mi ricordo che i programmatori di una volta diffidavano di make (almeno quelli che non venivano da Unix, cioè tutti).
La mia punizione è che il post è cortissimo, senza contenuto, sigh 😦 😦 😦
Però l’argomento successivo sarà tosto, assay 😀 forse.

Go – packages

go2

Go incoraggia le buone pratiche, tipo “non ripeterti!”
Per questo si usano le funzioni ma c’è un meccanismo successivo: i packages.
Come quando abbiamo usato

import "fmt"

fmt è il nome di un package che contiene svariate funzioni di formattazione e output.

Organizzare il codice in questo modo serve per tre propositi:

  • riduce la possibilità di avere conflitti con i nomi, questo consente di avere nomi corti per le funzioni;
  • organizza il codice e diventa facile trovare quello che vuoi riutilizzare;
  • velocizza la compilazione, essendo richiesto di compilare solo pezzi di un programma.

I packages hanno senso solo nel contesto di un programma che li usi. Esempio:
creo il main nella directory corrente

package main

import "fmt"
import "./math"

func main() {
    xs := []float64{1,2,3,4}
    avg := math.Average(xs)
    fmt.Println(avg)
}

creo la subdirectory math e dentro il file del package math.go

package math

func Average(xs []float64) float64 {
    total := float64(0)
    for _, x := range xs {
        total += x
    }
return total / float64(len(xs))

}

eseguo

mainPossiamo anche usare un alias, così

package main

import "fmt"
import m "./math"

func main() {
    xs := []float64{1,2,3,4}
    avg := m.Average(xs)
    fmt.Println(avg)
}

main-aNotare che la funzione si chiama Average, cioè inizia con una lettera maiuscola. Solo le entità che iniziano con una maiuscola sono visibili all’esterno del package in cui sono definite.

Ancora: i nomi dei packages corrispondono a quello della directory in cui sono definiti. C’è un modo per evitarlo ma va bene così (almeno per adesso).

Go è capace di generare automaticamente la documentazione per i nostri packages in modo simile a quelli standard

godoc

Così non dice granché ma inserendo un commento prima della dichiarazione della funzione si ha

godoc2

Go – buffered channels

Il post precedente su Go mi ha lasciato un dubbio: i buffered channels (rinuncio a tradurre, qualcuno mi aiuta?).
Googlando un po’ si trova di tutto sul Web, adesso vi relaziono (si può dire vero?).

bewaregopherParto con una cosa molto semplice, questa: Go by Example: Channel Buffering

package main

import "fmt"

func main() {
    messages := make(chan string, 2)
    messages <- "buffered"
    messages <- "channel"
    // Later we can receive these two values as usual.
    fmt.Println(<-messages)
    fmt.Println(<-messages)
}

bc0

il canale messages può contenere due stringhe che possono poi essere ricevuti nel solito modo. Fin qui niente di nuovo, a parte il fatto che il canale possa contenere più messaggi. Salto un suggerimento trovato su Stack Overflow, anche se di solito SO è una fonte sicura, una delle mie preferite, anche se non sempre citate come si dovrebbe 😉

OK, arrivo a Go Concurrency Patterns: Timing out, moving on.
Ahemmm… sono di nuovo a casa di Go, ci dovevo pensare prima, sto invecchiando (e non ci sono più le mezze stagioni!).

Si parte con un esempio semplice, già visto, non è il caso di ripeterlo qui. Poi si arriva al dunque, traduco.

In questo esempio abbiamo un programma che legge simultaneamente da più database replicati (caso tipico di Google). Al programma serve una sola risposta e quindi accetta la prima che arriva:

func Query(conns []Conn, query string) Result {
    ch := make(chan Result, 1)
    for _, conn := range conns {
        go func(c Conn) {
            select {
            case ch <- c.DoQuery(query):
            default:
            }
        }(conn)
    }
    return <-ch
}

In questo esempio la closure esegue un non-blocking send (di nuovo: non sono un traduttore, aiuto!) usando la send nell’istruzione select con un caso default. Essendo la send non bloccante viene garantito che nessuna delle goroutines lanciate blocchi il programma. Tuttavia se il risultato arriva prima che la funzione principale sia pronta a riceverlo la send può fallire perché niente è pronto. Questo è un problema noto (race condition)  che in questo caso viene risolto semplicemente con il canale con buffer di 1 garantendo che il primo valore inviato può essere ricevuto indipendentemente dall’ordine dell’esecuzione.

Considerazione mia: soluzione elegante, però diversa dall’usuale routine di programmazione 😉