Introduction
This lab introduces the concept of generics in Golang. Starting from version 1.18, Golang has added support for generics, which allows us to write more flexible and reusable code.
This lab introduces the concept of generics in Golang. Starting from version 1.18, Golang has added support for generics, which allows us to write more flexible and reusable code.
The problem to be solved in this lab is to understand how to define and use generic functions and types in Golang.
$ go run generics.go
keys: [4 1 2]
list: [10 13 23]
There is the full code below:
// Starting with version 1.18, Go has added support for
// _generics_, also known as _type parameters_.
package main
import "fmt"
// As an example of a generic function, `MapKeys` takes
// a map of any type and returns a slice of its keys.
// This function has two type parameters - `K` and `V`;
// `K` has the `comparable` _constraint_, meaning that
// we can compare values of this type with the `==` and
// `!=` operators. This is required for map keys in Go.
// `V` has the `any` constraint, meaning that it's not
// restricted in any way (`any` is an alias for `interface{}`).
func MapKeys[K comparable, V any](m map[K]V) []K {
r := make([]K, 0, len(m))
for k := range m {
r = append(r, k)
}
return r
}
// As an example of a generic type, `List` is a
// singly-linked list with values of any type.
type List[T any] struct {
head, tail *element[T]
}
type element[T any] struct {
next *element[T]
val T
}
// We can define methods on generic types just like we
// do on regular types, but we have to keep the type
// parameters in place. The type is `List[T]`, not `List`.
func (lst *List[T]) Push(v T) {
if lst.tail == nil {
lst.head = &element[T]{val: v}
lst.tail = lst.head
} else {
lst.tail.next = &element[T]{val: v}
lst.tail = lst.tail.next
}
}
func (lst *List[T]) GetAll() []T {
var elems []T
for e := lst.head; e != nil; e = e.next {
elems = append(elems, e.val)
}
return elems
}
func main() {
var m = map[int]string{1: "2", 2: "4", 4: "8"}
// When invoking generic functions, we can often rely
// on _type inference_. Note that we don't have to
// specify the types for `K` and `V` when
// calling `MapKeys` - the compiler infers them
// automatically.
fmt.Println("keys:", MapKeys(m))
// ... though we could also specify them explicitly.
_ = MapKeys[int, string](m)
lst := List[int]{}
lst.Push(10)
lst.Push(13)
lst.Push(23)
fmt.Println("list:", lst.GetAll())
}
Generics are a powerful feature that allows us to write more flexible and reusable code in Golang. With generics, we can define functions and types that work with any type, as long as they satisfy certain constraints. By using generics, we can reduce code duplication and improve code readability and maintainability.