๐ŸŽ“ A1Learner
๐Ÿ”
0 of 28 lessons completed

Go Generic Methods

A generic method is a method that carries its own type parameters. The method can work with a new type while the value it hangs on keeps its own type.

A Map method with its own type parameter

List holds ints here. Its Map method adds a type parameter U and builds strings from the ints.

package main

import "fmt"

type List[T any] struct {
	items []T
}

func (l List[T]) Map[U any](f func(T) U) []U {
	result := make([]U, len(l.items))
	for i, v := range l.items {
		result[i] = f(v)
	}
	return result
}

func main() {
	numbers := List[int]{items: []int{1, 2, 3}}
	words := numbers.Map(func(n int) string {
		return fmt.Sprintf("%d!", n)
	})
	fmt.Println(words)
}

A Filter method on the same List

Filter adds no new type parameter. It works with the T from List and keeps the values its test accepts.

package main

import "fmt"

type List[T any] struct {
	items []T
}

func (l List[T]) Filter(keep func(T) bool) []T {
	var result []T
	for _, v := range l.items {
		if keep(v) {
			result = append(result, v)
		}
	}
	return result
}

func main() {
	numbers := List[int]{items: []int{1, 2, 3, 4, 5, 6}}
	evens := numbers.Filter(func(n int) bool {
		return n%2 == 0
	})
	fmt.Println(evens)
}

A generic method on a plain type

Box is not a generic type. Its Show method still takes a type parameter and prints a value of any type beside the label.

package main

import "fmt"

type Box struct {
	label string
}

func (b Box) Show[T any](value T) {
	fmt.Println(b.label, value)
}

func main() {
	box := Box{label: "Value:"}
	box.Show(42)
	box.Show("go")
}

Example

package main

import "fmt"

type List[T any] struct {
	items []T
}

func (l List[T]) Map[U any](f func(T) U) []U {
	result := make([]U, len(l.items))
	for i, v := range l.items {
		result[i] = f(v)
	}
	return result
}

func (l List[T]) Filter(keep func(T) bool) []T {
	var result []T
	for _, v := range l.items {
		if keep(v) {
			result = append(result, v)
		}
	}
	return result
}

func main() {
	numbers := List[int]{items: []int{1, 2, 3, 4}}
	words := numbers.Map(func(n int) string {
		return fmt.Sprintf("%d!", n)
	})
	kept := List[string]{items: words}.Filter(func(s string) bool {
		return s != "3!"
	})
	fmt.Println(kept)
}

Key points

  • A method may carry its own type parameters, apart from the parameters of its receiver type.
  • The receiver type parameters come from the type the method hangs on.
  • Map on List[T] adds a second parameter U for the values it builds.
  • A method with no new type job can use the receiver parameter alone, as Filter does.
  • A plain non-generic type can also host a generic method.

In this example, Map first turns each int into a string with an exclamation mark. The strings move into a new List, and Filter drops the one equal to 3!. The kept strings print in order.

Note

A generic method is called like any other method, with the dot and the method name.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Go Playground

Exercise

Where do the receiver type parameters of a generic method come from?