Go Generics in Practice

Little snippets of Go generics I've written for real projects.

October 26, 2022

Map Access (and Set if absent)

func GetOrSetDefault[K comparable, V any](m map[K]V, key K, new_ func() V) V {
	value, found := m[key]
	if !found {
		value = new_()
		m[key] = value
	}
	return value
}

func main() {
	a := make(map[int]string)
	GetOrSetDefault(a, 42, func() string { return "hi" })
	fmt.Println(a[42]) // hi
}

Index a Slice of Objects into a Map

func Index[K comparable, V any](list []V, key func(V) K) map[K]V {
	result := make(map[K]V)
	for _, value := range list {
		result[key(value)] = value
	}
	return result
}

type Thing struct {
	Value string
}

func main() {
	a := []Thing{{Value: "a"}, {Value: "b"}}
	b := Index(a, func(t Thing) string { return t.Value })
	fmt.Println(b["a"]) // {a}
}

Fan-out/Fan-in

Inspired by code samples at "Go Concurrency Patterns: Pipelines and cancellation".

package main

import (
	"fmt"
	"log"
	"sync"
	"time"
)

func FanOut[T any](input chan T, workers int, work func(T) T) chan T {
	if workers <= minWorkerCount {
		panic("worker count must be positive")
	}
	if workers > maxWorkerCount {
		panic("are you sure you want that many workers?")
	}
	var outputs []chan T
	for x := 0; x < workers; x++ {
		output := make(chan T)
		outputs = append(outputs, output)
		go worker(input, output, work)
	}
	merged := make(chan T)
	go merge(outputs, merged)
	return merged
}
func worker[T any](input, output chan T, work func(T) T) {
	defer close(output)
	for item := range input {
		output <- work(item)
	}
}
func merge[T any](outputs []chan T, merged chan T) {
	defer close(merged)
	var waiter sync.WaitGroup
	defer waiter.Wait()
	waiter.Add(len(outputs))
	for _, output := range outputs {
		go drain(output, merged, waiter.Done)
	}
}
func drain[T any](output, merged chan T, done func()) {
	defer done()
	for item := range output {
		merged <- item
	}
}

const maxWorkerCount = 1024 * 10
const minWorkerCount = 0

func main() {
	var (
		workItemCount = 100
		workerCount   = 10
	)

	input := make(chan string)
	go func() {
		defer close(input)
		for x := 0; x < workItemCount; x++ {
			input <- fmt.Sprint(time.Now().Second())
		}
	}()

	started := time.Now()
	output := FanOut(input, workerCount, func(s string) string {
		time.Sleep(time.Second) // simulate long-running process
		return s + " " + fmt.Sprint(time.Now().Second())
	})

	for item := range output {
		log.Println(item)
	}

	actualDurationInSeconds := int(time.Since(started).Seconds())
	fmt.Println(actualDurationInSeconds) // 10
}