Rainer Stropek | @rstropek
package main
import (
"container/list"
"fmt"
)
type person struct {
name string
age int
}
func main() {
values := list.New()
var value interface{}
value = "FooBar"
values.PushBack(value)
value = 42
values.PushBack(value)
value = person{name: "FooBar", age: 42}
values.PushBack(value)
for v := values.Front(); v != nil; v = v.Next() {
fmt.Println(v.Value)
}
}
package main
import "fmt"
type hero struct {
name string
canFly bool
}
// Add the required method for the fmt.Stringer interface.
func (h hero) String() string {
return h.name
}
func main() {
var something interface{} = hero{name: "Homelander", canFly: true}
h, ok := something.(hero) // Use type assertation to check if something is a hero
if ok {
fmt.Println(h.name)
}
// Use type assertation to check if something fulfills the
// requirements of the fmt.Stringer interface.
var hStringer fmt.Stringer = something.(fmt.Stringer)
fmt.Println(hStringer.String())
}
We frequently need runtime type assertations
package main
import "fmt"
// Print prints all elements of the given slice to stdout
func Print[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}
func main() {
// Call print with explicit type parameter
Print[int]([]int{1, 2, 3})
Print[string]([]string{"Foo", "Bar"})
// Let go figure out the type parameter using type inference
Print([]int{1, 2, 3})
Print([]string{"Foo", "Bar"})
}
package main
import "fmt"
// Print prints all elements of the given slice to stdout
func Print[T any](s []T) {
for _, v := range s {
fmt.Println(v)
}
}
func Map[T1, T2 any](items []T1, mapFunc func(T1) T2) []T2 {
result := make([]T2, len(items))
for index, item := range items {
result[index] = mapFunc(item)
}
return result
}
func main() {
Print(Map([]int{1, 2, 3},
func(item int) bool { return item%2 == 0 }))
}
Type inference limitations
package main
import "fmt"
func concat[T any](c1, c2 <-chan T) <-chan T {
r := make(chan T)
go func(c1, c2 <-chan T, r chan<- T) {
defer close(r)
for v := range c1 {
r <- v
}
for v := range c2 {
r <- v
}
}(c1, c2, r)
return r
}
func main() {
c1 := make(chan string, 2)
c2 := make(chan string, 2)
go func() {
c1 <- "Hello"
c1 <- ", "
close(c1)
c2 <- "World"
c2 <- "!"
close(c2)
}()
c3 := concat(c1, c2)
for elem := range c3 {
fmt.Print(elem) // Will result
// in "Hello, World!"
}
}
// Generic predicate
// A function that takes any type T and returns a bool
type predicate[T any] func(item T) bool;
// Generic iterator function type
// A function that returns a reference to a type T
type iteratorFunc[T any] func() *T;
package main
import "fmt"
type hero struct {
name string
canFly bool
}
// Add the required method for the fmt.Stringer interface.
func (h hero) String() string {
return h.name
}
type joiner interface {
join(string, fmt.Stringer) string
}
type commaJoiner struct{}
func (commaJoiner) join(agg string, s fmt.Stringer) string {
// This function is for demonstrating generics. It is a
// naive implementation of string concatination. In practice,
// prefer e.g. strings.Builder to minimize memory copying.
if len(agg) > 0 {
return agg + ", " + s.String()
}
return s.String()
}
func join[S fmt.Stringer](items []S, j joiner) string {
var result string
for _, item := range items {
// We can pass `item` to `join()`
// as the second argument because S
// has a `fmt.Stringer` constraint.
result = j.join(result, item)
}
return result
}
func main() {
heroes := []hero{
{name: "Homelander", canFly: true},
{name: "Starlight", canFly: false},
}
fmt.Print(join(heroes, commaJoiner{}))
}
Constraining types
using interfaces
package main
import "fmt"
// The adder interface contains datatypes that define the + operator.
type adder interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 |
~uint32 | ~uint64 | ~float32 | ~float64 | ~string
}
func aggregate[T adder](items []T, seed T) T {
result := seed
for _, item := range items {
// Generic method can use += because of the type list in the adder constraint.
result += item
}
return result
}
// Index fulfills the adder constraint because it's underlying type uint is part of adder's type list.
type index uint
func main() {
fmt.Println(aggregate([]int{1, 2, 3, 4}, 32)) // results in 42
fmt.Println(aggregate([]string{"4", "2"}, "")) // results in "42"
fmt.Println(aggregate([]index{21, 21}, 0)) // results in 42
// The next line fails because hero cannot satisfy adder
// fmt.Println(aggregate([]hero{{name: "Homelander", canFly: true},},
// hero{name: "Starlight", canFly: false}))
}
~ means: predeclared type
Β Β or underlying type
package main
import "fmt"
type vectorElement interface {
~int | ~uint | ~float32 | ~float64
}
type vector2d[T vectorElement] struct {
X T
Y T
}
func newVector2d[T vectorElement](x T, y T) vector2d[T] {
return vector2d[T]{X: x, Y: y}
}
func (v1 vector2d[T]) add(v2 vector2d[T]) vector2d[T] {
return vector2d[T]{X: v1.X + v2.X, Y: v1.Y + v2.Y}
}
func main() {
v1 := newVector2d(10, 10)
v2 := newVector2d(11, 11)
v3 := v1.add(v2)
fmt.Println(v3)
}
Type set
Generic type
Generic function
package main
import "fmt"
// Generic iterator function type
type iteratorFunc[T any] func() *T
// Generic function for iteration
func next[T any](iterator iteratorFunc[T]) *T { return iterator() }
// Generic function executing a given function for each item in iterator
func forEach[T any](iterator iteratorFunc[T], body func(T)) {
for ptr := next(iterator); ptr != nil; ptr = next(iterator) {
body(*ptr)
}
}
func numbersIterator(max int) iteratorFunc[int] {
current := 0
return func() *int {
if current >= max {
return nil
}
result := current
current++
return &result
}
}
func main() {
// Print numbers between 0 and 10 (excl.)
forEach(numbersIterator(10), func(n int) { fmt.Println(n) })
}
...
// Generic predicate
type predicate[T any] func(item T) bool
// Generic function filtering based on a given predicate
func filter[T any](iterator iteratorFunc[T], p predicate[T]) iteratorFunc[T] {
return func() *T {
var item *T
for item = next(iterator); item != nil && !p(*item); item = next(iterator) {
}
return item
}
}
...
func main() {
// Print even numbers between 0 and 10 (excl.)
forEach(
filter(
numbersIterator(10),
func(n int) bool { return n%2 == 0 }),
func(n int) { fmt.Println(n) })
}
...
// Generic function that generates an iterator from a given slice
func iteratorFromSlice[T any](items []T) iteratorFunc[T] {
return func() *T {
if len(items) < 1 {
return nil
}
firstItem := &items[0]
items = items[1:]
return firstItem
}
}
type user struct {
name string
age int
}
...
func main() {
users := []user{user{name: "Foo", age: 42}, user{name: "Bar", age: 43}, user{name: "FooBar", age: 44},}
// Print each user's name where the user name starts with Foo.
forEach(filter(iteratorFromSlice(users), func(u user) bool { return strings.HasPrefix(u.name, "Foo") }),
func(u user) { fmt.Printf("User is %s\n", u.name) })
}
Rainer Stropek | @rstropek