Skip to content

Instantly share code, notes, and snippets.

@Coutlaw
Last active December 17, 2021 17:42
Show Gist options
  • Save Coutlaw/6cdf5006aee8ef6d95d1d88ef239da78 to your computer and use it in GitHub Desktop.
Save Coutlaw/6cdf5006aee8ef6d95d1d88ef239da78 to your computer and use it in GitHub Desktop.
Generics in Go
// This is a function to pull all the keys from a map and return them
// Its bad because it only works for strings right now
func getKeys(m map[string]int) []string {
var keys []string
for k := range m {
keys = append(keys, k)
}
return keys
}
// If we wanted to use this function for more types we would have to do something like
func getKeys(m interface{}) ([]interface{}, error) { // Accept and return any element
switch t := m.(type) {
default:
return nil, fmt.Errorf("unknown type: %T", t) // Handle if a type isn't implemented yet
case map[string]int:
var keys []interface{}
for k := range t {
keys = append(keys, k)
}
return keys, nil
case map[int]string:
// ...
}
}
// Downsides to this
/*
First, it increases boilerplate code.
Since it accepts an emtpy interface, we loose some benefits of a staticly typed lanaguage
Since we are handling only 2 types, we have to by default handle all other cases
*/
// Enter generics
// The keys are comparable, whereas values are of any type
func getKeys[K comparable, V any](m map[K]V) []K {
var keys []K // Create the keys slice
for k := range m {
keys = append(keys, k)
}
return keys
}
// Now our function can handle any map[K]V where K is comparable
// If we want to restrict types we can do something like this
type customConstraint interface {
~int | ~string // Define a custom type that will restrict types to int and string
}
// (~) just means accept any type who's underlying type resolves to the requested type
// Change the type parameter K to be custom
func getKeys[K customConstraint, V any](m map[K]V) []K {
// Same implementation
}
// with our custom type the function call looks like this
m = map[string]int{
"one": 1,
"two": 2,
"three": 3,
}
keys := getKeys(m)
// without the custom type we need to provide type arguments to the function calls
keys := getKeys[string](m)
// GENERICS in data structures
// A generic linked list
type Node[T any] struct { // Use type parameter
Val T
next *Node[T]
}
func (n *Node[T]) Add(next *Node[T]) { // Instantiate type receiver
n.next = next
}
// Generics can't be used on methods ..... (lame)
// meaning this would not compile
type Foo struct {}
func (Foo) bar[T any](t T) {}
// Generics as a sorting package
type sliceFn[T any] struct { // Use type parameter
s []T
compare func(T, T) bool // Compare two T elements
}
func (s sliceFn[T]) Len() int { return len(s.s) }
func (s sliceFn[T]) Less(i, j int) bool { return s.compare(s.s[i], s.s[j]) }
func (s sliceFn[T]) Swap(i, j int) { s.s[i], s.s[j] = s.s[j], s.s[i] }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment