1 · ปัญหา: สองทางที่แย่ทั้งคู่
❌ ทางที่ 1 — copy แล้วแก้ type
func SumInts(values []int) int {
var total int
for _, v := range values {
total += v
}
return total
}
func SumFloat64s(values []float64) float64 {
var total float64
for _, v := range values {
total += v
}
return total
}
โค้ดเหมือนกันเป๊ะ ต่างแค่คำว่า int/float64
เจอบั๊ก = ต้องแก้ทุกก๊อป
❌ ทางที่ 2 — ใช้ any
func Sum(values []any) any {
var total any
for _, value := range values {
switch v := value.(type) {
case int:
total = total.(int) + v
case float64:
total = total.(float64) + v
}
}
return total
}
เขียนครั้งเดียวจริง แต่โค้ดนี้ระเบิด — ดู predict ข้างล่าง
fmt.Println(Sum([]any{1, 2, 3}))
ฟังก์ชัน Sum ฝั่งขวา (แบบ any) พิมพ์อะไรออกมา?
ไม่พิมพ์อะไรเลย — มัน panic ผมรันแล้ว:
panic: interface conversion: interface {} is nil, not int
ทำไม: var total any ทำให้ total เริ่มต้นเป็น
nil — ไม่ใช่ 0 พอวนรอบแรกเจอ case int
มันสั่ง total.(int) คือ "จงตีความ nil ว่าเป็น int"
ซึ่งทำไม่ได้ (บทเรียน 11
— type assertion แบบไม่มี comma-ok พังแล้วพังเลย)
ประเด็นที่สำคัญกว่านั้น: โค้ดนี้คอมไพล์ผ่าน
compiler ไม่มีทางรู้เลยว่ามีอะไรอยู่ใน any
— พังตอน production เท่านั้น
นี่คือเหตุผลที่ generics ไม่ใช่แค่ "ทางที่สวยกว่า" แต่เป็นทางที่ปลอดภัยกว่า
2 · ทางที่สาม: type parameter
อ่าน Number ให้ออกก่อน — ข้างในไม่ใช่รายชื่อ method
แต่เป็น "รายชื่อ type ที่ยอมรับ" เครื่องหมาย | อ่านว่า
"หรือ" (เป็น int ก็ได้ int64 ก็ได้
float64 ก็ได้) ส่วน ~ ข้างหน้าอ่านว่า
"type ใดก็ตามที่มี underlying type เป็นตัวนี้"
ซึ่งทำให้ type ที่คุณตั้งเองอย่าง type Score int ผ่านด่านไปด้วย
ตอนนี้จำแค่นี้พอ — หัวข้อ 7 จะรันให้ดูว่าถ้าลืม ~
แล้ว compiler ด่าว่าอะไร
type Number interface {
~int | ~int64 | ~float64
}
func Sum[T Number](values []T) T {
var total T // zero value ของ T = 0 ไม่ใช่ nil
for _, value := range values {
total += value // compiler รู้ว่า T บวกกันได้
}
return total
}
ints := Sum([]int{10, 20, 30}) // 60
floats := Sum([]float64{1.5, 2.5, 3.5}) // 7.5
ตำแหน่งของวงเล็บบอกว่าเป็นอะไร
func Sum [T Number] (values []T) T
─────┬───── ────┬──── ┬
│ │ └─ คืนค่าเป็น type เดียวกัน
│ └────────── parameter ปกติ ใช้ T ได้
└─────────────────────── type parameter
"T คือ type อะไรก็ได้ที่เข้าเกณฑ์ Number"
[] เหลี่ยม = ส่ง type เข้าไป · () กลม = ส่ง ค่า เข้าไป
เครื่องคั้นน้ำผลไม้เครื่องเดียวใช้ได้กับของหลายอย่าง — ใส่ส้มได้น้ำส้ม ใส่แครอทได้น้ำแครอท ไม่ต้องซื้อสองเครื่อง
สองอย่างที่เครื่องนี้ทำได้ ตรงกับ Sum[T Number] พอดี:
· ใส่อะไรลงไป ได้ของชนิดนั้นออกมา — ไม่ใช่ "น้ำอะไรก็ไม่รู้"
นี่คือความหมายของ T ที่โผล่ทั้งขาเข้าและขาออก
· มีป้ายบอกว่าใส่อะไรได้บ้าง — "ผลไม้กับผักเท่านั้น อย่าใส่ก้อนหิน"
ป้ายนั้นคือ Number
ที่ไม่เหมือนกัน: เครื่องคั้นรู้ว่ากำลังคั้นอะไรก็ตอนที่คุณยัดผลไม้เข้าไปแล้วจริง ๆ
แต่ Go ตัดสินว่า T คือ type อะไรตั้งแต่ตอน compile —
ถ้าเอาของที่ไม่เข้าเกณฑ์ไปใส่ มันจะไม่ยอม build ให้ตั้งแต่แรก ไม่ใช่ปล่อยผ่านแล้วไปพังตอนรัน
- type parameter
- ตัว
Tที่อยู่ในวงเล็บเหลี่ยม = "ช่องว่างที่ยังไม่รู้ว่าเป็น type อะไร" · เวลาคุยให้พูดว่า “ฟังก์ชันนี้มี type parameter กี่ตัว” แทนที่จะพูดว่า “ในวงเล็บเหลี่ยมมีอะไรบ้าง” - type argument
- type จริงที่ไปเติมในช่องว่างนั้นตอนเรียกใช้ · เขียน
Sum[int](...)แปลว่า “ส่งintเป็น type argument” — เทียบกับ argument ปกติที่เป็น ค่า ตัวนี้เป็น type - constraint
- "ป้ายบอกว่าใส่อะไรได้บ้าง" · ในตัวอย่างคือ
Number· ประโยคที่ใช้ได้จริง: “constraint ของ T ตัวนี้คืออะไร” เวลาเจอ error ที่ compiler ไม่ยอมให้เอา T ไปทำอะไร - generic function
- ฟังก์ชันที่มี type parameter · ตรงข้ามกับฟังก์ชันธรรมดาที่ผูกกับ type เดียวตายตัว · เวลาถาม AI ให้พิมพ์ว่า “เขียน generic function ใน Go ที่รับ slice ของ T” จะได้คำตอบตรงกว่าพิมพ์ว่า “ฟังก์ชันที่ใช้ได้หลาย type”
3 · [T any] ไม่เหมือน parameter ที่เป็น any
จุดนี้คนสับสนมากที่สุด เพราะคำว่า any โผล่ทั้งสองแบบ
แต่ความหมายคนละเรื่อง:
❌ value any — ลืม type ไปแล้ว
func Identity(value any) any {
return value
}
name := Identity("Alice")
// name เป็น any → ใช้กับ string ไม่ได้
fmt.Println(strings.ToUpper(
name.(string), // ต้อง assert เอง
))
คุณรู้ว่ามันคือ string แต่ compiler ไม่รู้ — ต้อง assert ทุกครั้ง และ assert ผิดเมื่อไหร่ = panic
✅ [T any] — จำ type ไว้
func Identity[T any](value T) T {
return value
}
name := Identity("Alice")
// name เป็น string เต็มตัว
fmt.Println(strings.ToUpper(name))
compiler เห็นว่าส่ง string เข้าไป
→ T = string → คืนค่าเป็น string
ไม่ต้อง assert ไม่มีทาง panic
สรุปสั้น: any ในฐานะ parameter แปลว่า
"ฉันไม่สนว่าเป็นอะไร" — ทิ้ง type ไว้ที่ประตู
[T any] แปลว่า "ฉันยังไม่รู้ว่าเป็นอะไร แต่ตอนเรียกจะรู้
และจะจำไว้ทั้งฟังก์ชัน" — type ยังอยู่ครบ
คุณเอาร่มไปฝากที่เคาน์เตอร์ มีสองแบบ
แบบไม่มีบัตร — พนักงานรับของไปวางกองรวมกัน ตอนมารับคุณต้องบอกเองว่า
"ของผมคือร่ม" ถ้าจำผิดบอกเป็น "หมวก" เรื่องแดงตรงนั้นเลย
นี่คือ value any — ตอนเอาค่าออกมาใช้ต้อง .(string) เอง
บอกผิดเมื่อไหร่โปรแกรมตายทันที
(ศัพท์ที่ Go ใช้เรียกอาการตายกลางทางแบบนี้คือ panic)
ที่ตายคือแบบดิบเท่านั้นนะ — s := x.(string) ·
ถ้าเขียนแบบถามก่อน s, ok := x.(string) จะได้ ok = false
แล้วเดินต่อได้ ไม่พัง (สำนวน comma-ok จาก
บทเรียน 11)
แบบมีบัตร — พนักงานเขียนบัตรให้ว่า "ร่ม" ตอนมารับยื่นบัตร ได้ของถูกชิ้นแน่นอน
ไม่ต้องเดาสักคำ
นี่คือ [T any] — ส่ง string เข้าไป compiler ออกบัตรให้ว่า
T = string แล้วจำไว้ทั้งฟังก์ชัน
ที่ไม่เหมือนกัน: ของที่ฝากไว้ไม่เคยเปลี่ยนเป็นอย่างอื่นเลยทั้งสองแบบ —
ค่าที่ใส่ใน any ก็ยังเป็น string ตัวเดิม สิ่งที่หายไปคือ
"บัตร" ที่บอก compiler ว่าข้างในคืออะไร
และบัตรใบนี้ compiler เป็นคนออกให้ ไม่ใช่คุณ — มันตัดสินตอนแปลโค้ด
จาก type ของค่าที่คุณส่งเข้าไป · คุณจะเลือก T เองตอนโปรแกรมรันไม่ได้
(Show[ตัวแปร](x) คอมไพล์ไม่ผ่าน) และเขียน switch T
เพื่อถามตรง ๆ ว่า T คืออะไรก็ไม่ได้ — T เป็นชื่อ type ไม่ใช่ค่า
- type safety
- สภาพที่ compiler การันตีให้ได้ว่า type ถูกต้อง โดยที่คุณไม่ต้องเดาเอง · ประโยคที่เอาไปพูดได้: “แบบ generic มัน type safe กว่า เพราะไม่ต้อง assert”
- type assertion
- การเขียน
x.(string)= ดึงค่าที่ซ่อนอยู่ใน interface ออกมา พร้อมตรวจว่าใช่ type นั้นจริงไหม — ไม่ใช่การแปลง type และใช้ได้เฉพาะกับค่าที่เป็น interface (เช่นany) เท่านั้น · เขียนกับintตรง ๆ compiler ด่าว่าis not an interface
คือการบอกเองว่าของคือชิ้นไหน ในเคาน์เตอร์แบบไม่มีบัตร ·x.(string)แบบดิบ = พังถ้าผิด ·v, ok := x.(string)= ถามก่อน ไม่พัง (comma-ok จาก บทเรียน 11) · เวลาถาม senior ให้พูดว่า “ตรงนี้ต้อง type assert ไหม” - compile time / runtime
- compile time = ตอนแปลโค้ด (ยังไม่ได้รัน) · runtime = ตอนโปรแกรมกำลังทำงานจริง · คำคู่นี้คือหัวใจของบทนี้ทั้งบท: “ปัญหานี้จับได้ตอน compile time หรือไปโผล่ตอน runtime”
- panic
- อาการที่โปรแกรม Go ตายกลางทางเพราะเจอสิ่งที่ทำต่อไม่ได้ · ต่างจาก compile error ตรงที่เกิดตอน runtime คือหลุดถึงมือผู้ใช้แล้ว · เวลาแปะ error ให้คนอื่นดู บอกไปเลยว่า “มัน panic ตรงบรรทัดนี้”
4 · comparable — เกณฑ์สำหรับของที่เทียบกันได้
any กว้างเกินไปสำหรับงานที่ต้องใช้ ==
Go มีเกณฑ์สำเร็จรูปให้ชื่อ comparable:
func Contains[T comparable](values []T, target T) bool {
for _, value := range values {
if value == target { // ใช้ == ได้เพราะ comparable
return true
}
}
return false
}
Contains([]int{10, 20, 30}, 20) // true
Contains([]string{"Go", "Java"}, "Go") // true
Contains(points, Coordinate{X: 10, Y: 20}) // true — struct ที่ทุก field เทียบได้
ที่ใช้บ่อยอีกที่คือ key ของ map — เพราะ Go บังคับอยู่แล้วว่า key ต้องเทียบได้:
func Keys[K comparable, V any](items map[K]V) []K {
keys := make([]K, 0, len(items))
for key := range items {
keys = append(keys, key)
}
return keys
}
⚠️ สไลด์เขียน // [Alice Bob] — อย่าเชื่อลำดับนั้น
ผมรัน Keys กับ map 4 ตัว 5 ครั้งติด ได้:
[Alice Bob Carol Dave] · [Carol Dave Alice Bob] ·
[Bob Carol Dave Alice] · … ไม่ซ้ำกันเลย
Go สุ่มลำดับ for range บน map โดยตั้งใจ
เพื่อไม่ให้ใครเผลอเขียนโค้ดที่พึ่งลำดับ
ถ้าต้องการลำดับแน่นอนต้อง sort.Strings(keys) เอง
5 · Type inference — ไม่ต้องบอก type ก็ได้
type inference = "compiler เดา type ให้เอง" ·
คุณส่ง []int เข้าไป มันก็รู้เองว่า T คือ int
ไม่ต้องพิมพ์บอกซ้ำ
// แบบกำหนดเอง — ชัดเจนแต่ยาว
Contains[int]([]int{10, 20, 30}, 20)
// แบบให้ compiler เดาเอาจาก argument — นิยมกว่า
Contains([]int{10, 20, 30}, 20)
ส่วนใหญ่เขียนแบบสั้นได้เลย
จะต้องระบุเองก็ต่อเมื่อ compiler เดาไม่ออก — คือกรณีที่ T
ไม่ได้โผล่ใน argument ที่ส่งเข้าไปเลย มีแต่ใน return type
พอไม่มีอะไรให้ดู มันก็เดาไม่ถูก
6 · Constraint — กำหนดว่า T ต้องทำอะไรได้บ้าง
constraint คือ "เกณฑ์" ที่บอกว่า T ต้องเป็น type แบบไหนถึงจะผ่าน
ทีนี้ [T any] แปลว่า "อะไรก็ได้" ซึ่งแปลว่าไม่มีเกณฑ์อะไรเลย
ผลคือ compiler ไม่ยอมให้คุณทำอะไรกับมัน นอกจากส่งต่อ —
เพราะมันไม่มีทางรับประกันได้ว่าของที่ส่งเข้ามาบวกกันได้:
func Sum[T any](values []T) T {
var total T
for _, value := range values {
total += value // ❌ compile error — T อาจเป็น struct ที่บวกไม่ได้
}
return total
}
วิธีแก้คือบอก compiler ว่า T แคบลงแค่ไหน — เขียนเป็น
interface ที่ระบุรายชื่อ type แทนรายชื่อ method:
| Constraint | มาจากไหน | ให้ทำอะไรได้ |
|---|---|---|
any | built-in | ส่งต่อ / เก็บ / เทียบกับ nil เท่านั้น |
comparable | built-in | == · != · ใช้เป็น map key |
cmp.Ordered | import "cmp" (Go 1.21+) | < > <= >= — ตัวเลขทุกชนิด + string |
constraints.Ordered | golang.org/x/exp | เหมือนกัน แต่เป็น package ทดลอง — ใช้ cmp แทน |
| เขียนเอง | คุณ | เท่าที่รายชื่อ type ในนั้นทำได้ร่วมกัน |
import "cmp"
func Min[T cmp.Ordered](a, b T) T {
if a < b {
return a
}
return b
}
Min(10, 20) // 10
Min(1.5, 2.5) // 1.5
Min("apple", "zoo") // apple — string เรียงได้เหมือนกัน
สังเกตว่า constraint คือ interface เหมือนเดิม —
แค่ข้างในเปลี่ยนจาก "รายชื่อ method" เป็น "รายชื่อ type"
Go ไม่ได้เพิ่มคำใหม่ในภาษา แต่ขยายความหมายของ interface ที่คุณรู้จักจาก
บทเรียน 10 แล้ว
เครื่องหมาย | อ่านว่า "หรือ" — ~int | ~float64
คือ "เป็น int ก็ได้ float64 ก็ได้"
7 · ~ — สัญลักษณ์ที่ทำให้ custom type ผ่านด่าน
type Integer interface { int | int64 } // ← ไม่มี ~
func Max[T Integer](a, b T) T {
if a > b { return a }
return b
}
type Score int // จากบทเรียน 09
var first Score = 80
var second Score = 95
fmt.Println(Max(first, second))
Score มี underlying type เป็น int อยู่แล้ว — ผ่านไหม?
ไม่ผ่าน ผมรันแล้วได้:
Score does not satisfy Integer (possibly missing ~ for int in Integer)
compiler บอกวิธีแก้ให้ในวงเล็บเลย —
int เฉย ๆ ใน constraint แปลว่า "ต้องเป็น int เป๊ะ ๆ"
Score ไม่ใช่ int มันคือ Score —
เรื่องเดิมจาก บทเรียน 09
ที่ว่า defined type เป็นคนละ type กับต้นแบบ
เติม ~ เข้าไปแล้วผ่านทันที:
type Integer interface { ~int | ~int64 }
// ~int อ่านว่า "type ใดก็ตามที่มี underlying type เป็น int"
// → int ผ่าน · Score ผ่าน · Temperature ผ่าน · UserID ผ่าน
int กับ ~int ต่างกันตรงนี้
type Score int type Temperature int int
│ │ │
└── underlying: int ─────┴──── underlying: int ─┘
│
┌──────────────────┴──────────────────┐
│ │
constraint int constraint ~int
รับเฉพาะตัวขวาสุด รับทั้งสามตัว
(Score, Temperature ตกด่าน) (ผ่านหมด)
type Score int คือการเอา int ใส่กล่อง แล้วแปะป้ายชื่อใหม่ว่า "Score"
— ของข้างในเหมือนเดิมเป๊ะ เปลี่ยนแค่ป้าย
ทีนี้ constraint ก็เหมือนยามหน้าประตูที่ตรวจกล่องก่อนให้เข้า และมีสองแบบ:
· int เฉย ๆ = ยามที่ดูแค่ป้ายหน้ากล่อง "ป้ายต้องเขียนว่า int เท่านั้น"
→ กล่องที่แปะว่า Score ตกด่าน ทั้งที่ข้างในเหมือนกันทุกอย่าง
· ~int = ยามที่เปิดดูข้างใน "ป้ายจะเขียนอะไรก็ได้ ขอแค่ข้างในเป็น int"
→ Score, Temperature ผ่านหมด (ตรงกับรูปข้างบน)
ที่ไม่เหมือนกัน: ยามต้องแกะกล่องดูของจริง แต่ compiler ไม่ต้องแกะอะไรเลย
มันอ่านจากบรรทัด type Score int ที่คุณเขียนไว้เอง — ตัดสินได้ตั้งแต่ยังไม่รันโปรแกรม
และการมีป้ายคนละใบไม่ใช่เรื่องไร้สาระ: มันคือสิ่งที่กันไม่ให้เผลอเอา
Score ไปบวกกับ Temperature — ข้างในเป็น int
เหมือนกันทั้งคู่ แต่ compiler ฟ้องว่า
mismatched types Score and Temperature
ใช้ได้กับ slice type ที่ตั้งเองด้วย:
type Names []string
type StringSlice interface { ~[]string }
func CountNames[S StringSlice](names S) int {
return len(names)
}
CountNames([]string{"Alice", "Bob"}) // ผ่าน
CountNames(Names{"Alice", "Bob"}) // ผ่านเพราะมี ~
เขียน constraint เอง → ใส่ ~ ไว้ก่อนแทบทุกครั้ง
เพราะโค้ดจริงเต็มไปด้วย type Baht int64, type UserID string
ที่คุณสร้างเองตามบทเรียน 09 — ถ้าไม่ใส่ ~ ฟังก์ชัน generic ของคุณ
จะใช้กับ type ของโปรเจกต์ตัวเองไม่ได้เลย
- underlying type
- "ของที่อยู่ข้างในกล่อง" ·
Scoreมี underlying type เป็นint· เวลาถามให้พูดว่า “underlying type ของตัวนี้คืออะไร” จะสื่อกว่าถามว่า “ตัวนี้จริง ๆ แล้วเป็น int ใช่ไหม” - defined type
- "กล่องที่แปะป้ายชื่อใหม่" — type ที่คุณประกาศเองด้วย
type Score int· Go ถือว่ามันเป็นคนละ type กับintเสมอ (เรื่องเดิมจาก บทเรียน 09) - type set
- "รายชื่อ type ทั้งหมดที่ constraint ตัวหนึ่งยอมรับ" ·
~int | ~int64มี type set ที่กว้างกว่าint | int64· ประโยคที่ใช้ได้จริงตอนเจอ error: “type set ของ constraint นี้ครอบคลุม defined type ด้วยไหม” - satisfy
- คำที่ compiler ใช้เวลา type ผ่าน/ไม่ผ่าน constraint —
Score does not satisfy Integerอ่านว่า "Score ไม่เข้าเกณฑ์ Integer" · เอาไปพิมพ์ถาม AI ได้ทั้งประโยค จะตรงกว่าถามว่า “ทำไมมัน error”
ทดสอบความเข้าใจ
1. func Sum(values []any) any ที่มี var total any มีปัญหาอะไร?
var total any ได้ nil ไม่ใช่ 0 →
total.(int) รอบแรกคือ
panic: interface conversion: interface {} is nil, not int
อันตรายตรงที่คอมไพล์ผ่าน — compiler มองไม่เห็นข้างใน any
จึงไปพังเอาตอนรันจริง
2. func F[T any](v T) T ต่างจาก func F(v any) any ยังไง?
ส่ง string เข้า F[T any] → ได้ string กลับมา
เรียก strings.ToUpper ต่อได้ทันที
ส่งเข้า F(v any) → ได้ any กลับมา
ต้อง .(string) เอง และถ้า assert ผิด = panic
สรุป: generic รักษา type ไว้ · any ทิ้ง type
3. type Score int ใช้กับ interface{ int | int64 } ไม่ได้ เพราะอะไร?
ในตัว constraint int = "ต้องเป็น int เท่านั้น" ·
~int = "อะไรก็ได้ที่ underlying เป็น int"
compiler บอกทางแก้ให้ตรง ๆ:
possibly missing ~ for int in Integer
เชื่อมกับบทเรียน 09: Score เป็นคนละ type กับ int เสมอ
4. อยากเขียน Min[T](a, b T) T ควรใช้ constraint ตัวไหน?
comparable ให้แค่ == และ !=
ไม่ให้ < — ชื่อชวนเข้าใจผิดมากที่สุดใน Go
ต้องใช้ cmp.Ordered (มีใน standard library ตั้งแต่ Go 1.21)
ซึ่งครอบคลุมตัวเลขทุกชนิดและ string
ทำใน Go Playground หรือในเครื่อง สร้างไฟล์เดียวจบ
- เขียน
Sum[T Number](values []T) Tโดยที่Numberเป็น constraint ของคุณเอง ใส่~ให้ครบทุกตัว - ทดสอบว่าใช้ได้จริงกับ 3 แบบ:
[]int·[]float64· และ[]Bahtเมื่อtype Baht int64(ของจากบทเรียน 09) - เขียน
Max[T cmp.Ordered](values []T) (T, bool)— คืนfalseเมื่อ slice ว่าง
ใบ้: ค่าที่คืนตอนว่างคือvar zero T - เขียน
Contains[T comparable](values []T, target T) boolแล้วลองใช้กับ[]struct{X, Y int} - ท้าทาย 1: ลองเอา
~ออกจากNumberแล้วรัน — จดข้อความ error ที่ได้ทั้งบรรทัด แล้วดูว่า compiler บอกทางแก้ไว้ตรงไหน - ท้าทาย 2: ลองเปลี่ยน
Maxให้ใช้comparableแทนcmp.Ordered— พังตรงไหน และ error ว่าอะไร? - ท้าทาย 3: เขียน
Contains[T comparable]แล้วส่ง[][]int(slice ของ slice) เข้าไป — ผ่านไหม? ทำไม?
ใบ้: slice ใน Go เอามา==กันได้หรือเปล่า
ท้าทาย 2: พังที่บรรทัดที่ใช้ >:
invalid operation: value > best (type parameter T cannot use operator >)
ชื่อ comparable ชวนเข้าใจผิดที่สุดใน Go —
ภาษาไทยแปลว่า "เปรียบเทียบได้" ทำให้นึกถึง < > ด้วย
แต่จริง ๆ มันหมายถึงแค่ "เอามาเทียบว่าเท่ากันไหมได้" —
คือ == กับ != เท่านั้น
ของที่เรียงลำดับได้ต้องใช้ cmp.Ordered
ท้าทาย 3: ไม่ผ่าน —
[]int does not satisfy comparable
เพราะ Go ไม่ให้ == กับ slice (เทียบได้เฉพาะกับ nil)
เหตุผลเดียวกับที่ slice เป็น map key ไม่ได้
ถ้าอยากเทียบต้องใช้ slices.Equal
💬 ถามได้เลย — โดยเฉพาะถ้า error ตอนทำท้าทายอ่านไม่ออก
ข้อความ error ของ generics เป็นจุดที่ Go อ่านยากที่สุด
ส่งมาทั้งบรรทัดได้เลย
บทถัดไปเอา generics ไปใส่ใน struct — และดูว่า
เมื่อไหร่ที่ยังไม่ควรใช้ generics ซึ่งสำคัญพอ ๆ กัน
อ้างอิง
- Tutorial: Getting started with generics — ทางการ เริ่มจาก
Sumแบบเดียวกับบทนี้ - An Introduction To Generics — Go Blog ตอนปล่อย Go 1.18
- Go Spec — Type parameter declarations — ตัวบทกฎหมายเรื่อง
~และ type set - pkg.go.dev — cmp.Ordered
- Go maps in action — ที่มาของการสุ่มลำดับ
for rangeบน map