Generics
TypeScript generics vs Go generics
หัวข้อที่มีชื่อว่า “TypeScript generics vs Go generics”TypeScript generics อยู่ทุกที่ — คุณเขียน Array<T>, Promise<T>, Record<K, V> กันทุกวัน Go เพิ่ม generics เข้ามาใน 1.18 syntax คล้ายกัน แต่โมเดล constraint ต่างกัน
ความต่างที่สำคัญ: TypeScript ใช้ structural typing กับ generics (T extends { length: number }) ส่วน Go ใช้ constraint interface — interface ที่มีชื่อ ซึ่งระบุชัดเจนว่ารับ type ไหนได้บ้าง (หรือต้องมี method set อะไร) ทำให้ generics ของ Go ชัดเจนและคาดเดาง่ายกว่า แลกกับความ verbose ที่มากขึ้น
// TypeScript — structural constraintfunction identity<T>(value: T): T { return value;}
function first<T extends { length: number }>(arr: T[]): T { return arr[0];}
// Generic containerclass Stack<T> { private items: T[] = []; push(item: T) { this.items.push(item); } pop(): T | undefined { return this.items.pop(); }}// Go — type parameter + constraint interfacefunc Identity[T any](value T) T { return value}
func First[T any](slice []T) T { return slice[0]}
// Generic stacktype 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 } last := s.items[len(s.items)-1] s.items = s.items[:len(s.items)-1] return last, true}Constraint interfaces
หัวข้อที่มีชื่อว่า “Constraint interfaces”constraint any หมายถึง “type อะไรก็ได้” — เทียบเท่า interface{} ในโลกของ generics แต่ Go ให้คุณเขียน constraint ที่ละเอียดกว่านั้นได้ ด้วย interface ที่ระบุ underlying type ผ่าน operator ~
// TypeScript — union type constrainttype Numeric = number | bigint;
function add<T extends Numeric>(a: T, b: T): T { return (a as any) + (b as any) as T;}// Go — constraint interface พร้อม type uniontype Number interface { ~int | ~int32 | ~int64 | ~float64}
func Add[T Number](a, b T) T { return a + b}
// การใช้งานx := Add(1, 2) // inty := Add(1.5, 2.5) // float64syntax ~int หมายถึง “type ใดก็ตามที่มี underlying type เป็น int” ดังนั้น custom type อย่าง type MyID int ก็เข้าเงื่อนไข ~int ด้วย ถ้าไม่มี ~ จะตรงเฉพาะ type int ตรง ๆ เท่านั้น
constraint comparable
หัวข้อที่มีชื่อว่า “constraint comparable”TypeScript เทียบอะไรก็ได้ด้วย === ส่วน operator == ของ Go ใช้ได้เฉพาะกับ type ที่ comparable เท่านั้น (ไม่ใช่ slice, map หรือ function) constraint comparable ที่มีมาในตัวสื่อถึงข้อจำกัดนี้:
// TypeScript — == ใช้ได้กับทุกอย่างfunction contains<T>(arr: T[], val: T): boolean { return arr.some(x => x === val);}// Go — ต้องใช้ comparable constraint สำหรับ ==func Contains[T comparable](slice []T, val T) bool { for _, v := range slice { if v == val { return true } } return false}
// ใช้ได้กับ int, string, struct ที่มีฟิลด์ comparable — แต่ไม่ใช่ slices หรือ mapsรวม method sets กับ type unions
หัวข้อที่มีชื่อว่า “รวม method sets กับ type unions”constraint interface รวมทั้ง method requirement และ type union ไว้ด้วยกันได้:
type Stringer interface { String() string}
// รับ type ใดก็ตามที่เป็น int หรือ float64 และมี String() method// (พบได้น้อยแต่ถูกต้องในโค้ดไลบรารี)type StringableNumber interface { ~int | ~float64 String() string}ในทางปฏิบัติ constraint ส่วนใหญ่เป็น method set ล้วน ๆ (classic interface) หรือไม่ก็ type union ล้วน ๆ (งานคำนวณตัวเลข/การเรียงลำดับ)
ตัวอย่างในโลกจริง: Map และ Filter
หัวข้อที่มีชื่อว่า “ตัวอย่างในโลกจริง: Map และ Filter”// TypeScript — stdlib มี built-in เหล่านี้const doubled = [1,2,3].map(x => x * 2);const evens = [1,2,3,4].filter(x => x % 2 === 0);// Go — ก่อน 1.21 คุณต้องเขียนเองเหล่านี้func Map[T, U any](s []T, fn func(T) U) []U { out := make([]U, len(s)) for i, v := range s { out[i] = fn(v) } return out}
func Filter[T any](s []T, fn func(T) bool) []T { var out []T for _, v := range s { if fn(v) { out = append(out, v) } } return out}
// ตั้งแต่ Go 1.23 slices.Collect + iter package จัดการได้ idiomatic มากขึ้นGenerics vs interfaces — เมื่อใดควรใช้อะไร
หัวข้อที่มีชื่อว่า “Generics vs interfaces — เมื่อใดควรใช้อะไร”ลองดูเลย
หัวข้อที่มีชื่อว่า “ลองดูเลย”package main
import "fmt"
type Number interface { ~int | ~float64}
func Sum[T Number](nums []T) T { var total T for _, n := range nums { total += n } return total}
func Map[T, U any](slice []T, fn func(T) U) []U { result := make([]U, len(slice)) for i, v := range slice { result[i] = fn(v) } return result}
func Contains[T comparable](slice []T, val T) bool { for _, v := range slice { if v == val { return true } } return false}
func main() { ints := []int{1, 2, 3, 4, 5} fmt.Println("Sum:", Sum(ints)) fmt.Println("Sum floats:", Sum([]float64{1.1, 2.2, 3.3}))
doubled := Map(ints, func(x int) int { return x * 2 }) fmt.Println("Doubled:", doubled)
labels := Map(ints, func(x int) string { return fmt.Sprintf("item-%d", x) }) fmt.Println("Labels:", labels)
fmt.Println("Contains 3?", Contains(ints, 3)) fmt.Println("Contains 9?", Contains(ints, 9))}Loading Go runtime (first run only, ~8 MB)…