26 October 2022

Go Generics in Practice

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

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}
}

Coalesce Function:

// coalesce returns the first non-zero-value argument.
// use !reflect.DeepEqual(item, zero) and change T to `any`
// if you want to compare slices.
func coalesce[T comparable](values ...T) (zero T) {
	for _, item := range values {
		if item != zero {
			return item
		}
	}
	return zero
}

func main() {
	fmt.Println(coalesce("a", "b")) // "a"
	fmt.Println(coalesce("", "b")) // "b"
}

Load a channel from a slice:

func Slice2Channel[T any](slice []T) chan T {
	channel := make(chan T)
	go func() {
		for _, item := range slice {
			channel <- item
		}
	}()
	return channel
}

Drain a channel into a slice:

func Channel2Slice[T any] (channel chan T) (slice []T) {
	for item := range channel {
		slice = append(slice, item)
	}
	return slice
}