My Go Journey

23 June 2021

Vladislav Matúš

Software developer, Nextsum

Learning a programming language

2

Go Libraries Overview

3

Process of learning - Case study

Task: Program that counts a percentage of a specified letter in all files in a directory

func percentage(input []byte, letter byte) float64 {
    var letterCount int
    for _, b := range input {
        if b == letter {
            letterCount++
        }
    }
    return 100.0 * float64(letterCount) / float64(len(input))
}

We will see soon how we can improve this

4

Process of learning

Very first version of the main function's code:

func main() {
    const requestedLetter = 'a'
    const dataDir = "../data"
    files, _ := os.ReadDir(dataDir)
    for _, file := range files {
        filePath := path.Join(dataDir, file.Name())
        fileContents, err := os.ReadFile(filePath)
        if err != nil { /* error handling */ }
        pct := percentage(fileContents, requestedLetter)
        fmt.Printf("%f%% of %q in file %s\n", pct, requestedLetter, filePath)
    }
}

And now the curiosity kicks in:

5

Can we get the variables from the user?

YES, we can! (flag package)

    requestedLetterIn := flag.String("l", "", "letter that we want to count")
    dataDir := flag.String("d", "", "the directory")
    flag.Parse()
    if len(*requestedLetterIn) != 1 {
        return
    }
    requestedLetter := (*requestedLetterIn)[0]

Usage:

go run . -d ../data -l a

6

Can we avoid loading the whole file to memory?

YES, we can! (io.Reader)

        f, err := os.Open(filePath)
        if err != nil { /* error handling */ }
        pct := percentage(f, requestedLetter)
        if err = f.Close(); err != nil { /* error handling */ }
        fmt.Printf("%f%% of %q in file %s\n", pct, requestedLetter, filePath)
func percentage(r io.Reader, letter byte) float64 {
    var letterCount, allCount int
    bufR := bufio.NewReader(r)
    for {
        b, err := bufR.ReadByte()
        if err != nil {
            break
        }
        if b == letter {
            letterCount++
        }
        allCount++
    }
    return 100.0 * float64(letterCount) / float64(allCount)
}
7

Can we make it concurrent?

YES, we can!

    filePathCh := make(chan string)
    var workerWg sync.WaitGroup
    // We create a worker pool and thereby limit the maximal number of go routines
    for i := 0; i < runtime.NumCPU(); i++ {
        workerWg.Add(1)
        go func(workerNo int) {
            defer workerWg.Done()
            for filePath := range filePathCh {
                f, err := os.Open(filePath)
                if err != nil { /* error handling */
                }
                pct := percentage(f, requestedLetter)
                if err = f.Close(); err != nil { /* error handling */
                }
                fmt.Printf("%f%% of %q in file %s processed by worker %d\n",
                    pct, requestedLetter, filePath, workerNo)
            }
        }(i)
    }
    for _, file := range files {
        filePathCh <- path.Join(*dataDir, file.Name())
    }
    close(filePathCh)
    workerWg.Wait()
8

"Phonebook" of learning resources

9

The community

10

The Go Gopher

11

Thank you

Vladislav Matúš

Software developer, Nextsum