เรียน Go ภาษาง่าย 📘

🧬

เจาะลึก Generics ใน Go ฉบับภาษาง่าย

constraints, comparable, inference 1.27

8 บท
12 ข้อสอบ
1. ทำไมต้องมี Generics

เมื่อก่อนอยากได้ Min ทั้ง int และ float ต้องเขียน 2 รอบ:

go
func MinInt(a, b int) int { if a < b { return a }; return b }
func MinFloat(a, b float64) float64 { if a < b { return a }; return b }

มี generics เขียนรอบเดียวจบ

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


2. เขียน func ครอบจักรวาล
go
func Min[T int | float64](a, b T) T {
    if a < b { return a }
    return b
}
Min(1, 2)       // T=int
Min(1.5, 2.5)   // T=float64
Min[int](1, 2)  // เขียนชัดก็ได้

[T int|float64] อ่านว่า "T เป็นได้แค่ int หรือ float64"

ทำกับ slice ได้:

go
func Map[T, R any](s []T, f func(T) R) []R {
    out := make([]R, 0, len(s))
    for _, v := range s { out = append(out, f(v)) }
    return out
}
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


3. Constraints + ~

any = อะไรก็ได้, comparable = เทียบ == ได้ (เอาไว้ทำ key map)

go
type Number interface { ~int | ~int64 | ~float64 }
// ~ = เอา type ที่ไส้เป็น int ด้วย เช่น type Age int ก็ได้
✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


4. ของใหม่ 1.26: Recursive constraint

Generic อ้างตัวเองได้แล้ว ทำโครงสร้างซับซ้อนง่ายขึ้น:

go
type Node[T Node[T]] interface { Next() T }
// T ต้องเป็น Node เหมือนกัน ไว้ทำ tree/graph

มือกลางรู้ว่ามีก็พอ ได้ใช้ตอนทำ lib ซับซ้อน

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


5. ของใหม่ 1.27: Generic methods + inference ทุกที่

เมื่อก่อน method ห้าม generic ตอนนี้ได้แล้ว (ตัวอย่างจาก math/rand/v2):

go
func (r *Rand) N[Int intType](n Int) Int
// เมธอดเดียวใช้ทุก int ไม่ต้องแยก Int32N Int64N

และ compiler เดา type ให้ในที่ที่เคยต้องย้ำ:

go
func GenericFormatter[T any](v T) string { return fmt.Sprintf("value: %v", v) }
type IntFormatter func(int) string

formatters := []IntFormatter{GenericFormatter} // เดา T=int
fn := IntFormatter(GenericFormatter)
ch := make(chan IntFormatter, 1)
ch <- GenericFormatter // ส่งเข้า channel ก็เดาให้

ถ้าเดาไม่ออกให้เขียนชัด GenericFormatter[int] อย่าฝืน

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


6. เมื่อไหร่ไม่ควรใช้
  • ใช้แค่ 1-2 type เขียนแยกง่ายกว่า อ่านง่ายกว่า
  • อยากได้พฤติกรรมต่างกันมากๆ ใช้ interface ดีกว่า
  • โค้ด generic อ่านยาก ให้เริ่มจาก any + เทสต์ก่อนค่อยตีกรอบ

อ้างอิง: Go 1.26 release (recursive), Go 1.27 release (generic methods + inference incl. channel sends)

✏️ แบบทดสอบท้ายบท
แบบทดสอบท้ายบท
0/2


ภาคผนวก: ตัวอย่างเพิ่ม + Best Practices
ตัวอย่างเพิ่ม 1: หาตำแหน่งด้วย comparable
go
func IndexOf[T comparable](xs []T, want T) int {
    for i, x := range xs {
        if x == want {
            return i
        }
    }
    return -1
}
ตัวอย่างเพิ่ม 2: บวกเลขทุกชนิดด้วย constraint
go
func Sum[T ~int | ~float64](xs []T) T {
    var total T
    for _, x := range xs {
        total += x
    }
    return total
}

~ = รับ type พื้นฐานที่ตั้งชื่อใหม่ด้วย (เช่น type Age int)

ตัวอย่างเพิ่ม 3: struct generic (คิว)
go
type Queue[T any] struct{ items []T }

func (q *Queue[T]) Push(x T) { q.items = append(q.items, x) }
func (q *Queue[T]) Pop() (T, bool) {
    if len(q.items) == 0 {
        var zero T
        return zero, false
    }
    x := q.items[0]
    q.items = q.items[1:]
    return x, true
}
Best practices เช็กลิสต์
  • เริ่มจาก any + เทสต์ก่อน ค่อยตีกรอบ constraint ทีหลัง
  • ใช้ comparable เมื่อต้องการ == ใช้ ~ เมื่อรับ type พื้นฐานที่ตั้งชื่อใหม่
  • constraint ตั้งชื่อ (type Number interface{...}) อ่านง่ายกว่าเขียนยาวทุก func
  • แค่ 1-2 type อย่าใช้ generics เขียนแยก 2 func อ่านง่ายกว่า
  • อย่า generic เกิน 2-3 params คนอ่านตามไม่ทัน
  • error ยังต้อง return ปกติ generics ไม่ได้ช่วยเรื่องนี้
แบบฝึกหัด
  • เขียน Keys(m map[K]V) []K ดึง keys ออกมา
  • ทำ Set[T comparable] ด้วย map แล้วเทสต์ add/has