Last active
December 17, 2021 17:42
-
-
Save Coutlaw/6cdf5006aee8ef6d95d1d88ef239da78 to your computer and use it in GitHub Desktop.
Generics in Go
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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