Generics

Generics — Lập trình tổng quát (Go 1.18+)

Trước Go 1.18, muốn viết hàm hoạt động với nhiều kiểu, phải dùng interface{} và type assertion. Generics cho phép viết type-safe code tổng quát.

// Không có generics — phải viết cho từng kiểu
func SumInt(s []int) int { ... }
func SumFloat(s []float64) float64 { ... }

// Với generics — viết một lần, dùng cho mọi kiểu số
func Sum[T int | float64](s []T) T {
    var total T
    for _, v := range s {
        total += v
    }
    return total
}

Sum([]int{1, 2, 3})        // 6
Sum([]float64{1.5, 2.5})   // 4.0

Type Constraint

import "golang.org/x/exp/constraints"

// Định nghĩa constraint
type Number interface {
    int | int8 | int16 | int32 | int64 |
    float32 | float64
}

// Dùng constraint có sẵn
func Min[T constraints.Ordered](a, b T) T {
    if a < b { return a }
    return b
}

Min(3, 5)           // 3 (int)
Min(3.14, 2.71)     // 2.71 (float64)
Min("apple", "zen") // "apple" (string)

Generic Type (Struct)

// Stack generic
type Stack[T any] struct {
    items []T
}

func (s *Stack[T]) Push(item T) {
    s.items = append(s.items, item)
}

func (s *Stack[T]) Pop() (T, bool) {
    var zero T
    if len(s.items) == 0 {
        return zero, false
    }
    n := len(s.items) - 1
    item := s.items[n]
    s.items = s.items[:n]
    return item, true
}

// Dùng với bất kỳ kiểu nào
intStack := Stack[int]{}
intStack.Push(1)
intStack.Push(2)

strStack := Stack[string]{}
strStack.Push("hello")

Các hàm functional với Generics

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

func Filter[T any](s []T, f func(T) bool) []T {
    var result []T
    for _, v := range s {
        if f(v) { result = append(result, v) }
    }
    return result
}

func Reduce[T, U any](s []T, init U, f func(U, T) U) U {
    result := init
    for _, v := range s {
        result = f(result, v)
    }
    return result
}

// Dùng
nums := []int{1, 2, 3, 4, 5}
doubled := Map(nums, func(n int) int { return n * 2 })
evens   := Filter(nums, func(n int) bool { return n%2 == 0 })
total   := Reduce(nums, 0, func(acc, n int) int { return acc + n })
example.go
package main

import "fmt"

type Ordered interface {
	~int | ~float64 | ~string
}

func Min[T Ordered](a, b T) T {
	if a < b {
		return a
	}
	return b
}

func Max[T Ordered](a, b T) T {
	if a > b {
		return a
	}
	return b
}

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

func (s *Stack[T]) Push(v T)         { s.items = append(s.items, v) }
func (s *Stack[T]) Size() int        { return len(s.items) }
func (s *Stack[T]) IsEmpty() bool    { return len(s.items) == 0 }
func (s *Stack[T]) Pop() (T, bool) {
	var zero T
	if s.IsEmpty() {
		return zero, false
	}
	n := len(s.items) - 1
	v := s.items[n]
	s.items = s.items[:n]
	return v, true
}

func Map[T, U any](s []T, f func(T) U) []U {
	r := make([]U, len(s))
	for i, v := range s {
		r[i] = f(v)
	}
	return r
}

func Filter[T any](s []T, f func(T) bool) []T {
	var r []T
	for _, v := range s {
		if f(v) {
			r = append(r, v)
		}
	}
	return r
}

func Reduce[T, U any](s []T, init U, f func(U, T) U) U {
	r := init
	for _, v := range s {
		r = f(r, v)
	}
	return r
}

func main() {
	fmt.Println(Min(3, 7))
	fmt.Println(Min("banana", "apple"))

	s := Stack[string]{}
	for _, w := range []string{"Go", "is", "awesome"} {
		s.Push(w)
	}
	for !s.IsEmpty() {
		if v, ok := s.Pop(); ok {
			fmt.Print(v, " ")
		}
	}
	fmt.Println()

	nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	squares := Map(nums, func(n int) int { return n * n })
	evens := Filter(nums, func(n int) bool { return n%2 == 0 })
	total := Reduce(nums, 0, func(a, b int) int { return a + b })
	fmt.Println("Bình phương:", squares)
	fmt.Println("Số chẵn:   ", evens)
	fmt.Println("Tổng:      ", total)
}