Generics — Go เทียบกับ TypeScript
Generics ของ TypeScript: structural และยืดหยุ่นมาก
หัวข้อที่มีชื่อว่า “Generics ของ TypeScript: structural และยืดหยุ่นมาก”Generics ของ TypeScript ใช้ structural typing — type ใดก็ตามที่มีรูปร่าง (shape) ตรงกันจะถือว่าผ่าน constraint ได้ แม้จะไม่ได้ประกาศไว้ชัดเจนก็ตาม นอกจากนี้ TypeScript ยังมี conditional types (T extends U ? X : Y), mapped types ({ [K in keyof T]: ... }) และ template literal types อีกด้วย
Generics ของ Go (เพิ่มเข้ามาใน Go 1.18) เรียบง่ายและชัดเจนกว่า ทุก type parameter ต้องผ่าน interface constraint และ Go ไม่มี conditional types, mapped types หรือ template literal types
Generic function พื้นฐาน
หัวข้อที่มีชื่อว่า “Generic function พื้นฐาน”syntax คล้ายกัน: ใช้วงเล็บเหลี่ยมสำหรับ type parameters
// TypeScript — structural genericfunction first<T>(arr: T[]): T | undefined { return arr[0];}
// Works for any type — no explicit constraint neededfirst([1, 2, 3]); // numberfirst(["a", "b"]); // stringfirst([{ id: 1 }]); // object
// Constrained genericfunction getLength<T extends { length: number }>(v: T): number { return v.length; // structural: any type with .length works}getLength("hello");getLength([1, 2, 3]);getLength({ length: 5, name: "x" }); // also works — has .length// Go — type parameter with explicit constraintfunc First[T any](arr []T) (T, bool) { var zero T if len(arr) == 0 { return zero, false } return arr[0], true}
// Constrained generic — must specify the constraint interfacetype Lengther interface { Len() int // explicit method — not structural!}
// Without a constraint, you can only use operations valid for 'any':// == is NOT valid for 'any' unless you add 'comparable' constraintfunc Contains[T comparable](slice []T, item T) bool { for _, v := range slice { if v == item { return true } } return false}Union type constraints ด้วย ~
หัวข้อที่มีชื่อว่า “Union type constraints ด้วย ~”constraint ใน Go เขียน syntax อยู่ใน body ของ interface คุณระบุ concrete type ด้วย | เพื่อทำ type union ได้ และ ~T หมายถึง “type ใดก็ตามที่มี underlying type เป็น T” (ครอบคลุม custom type อย่าง type MyInt int)
// TypeScript — conditional type for numeric uniontype Numeric = number | bigint;function add<T extends Numeric>(a: T, b: T): T { return (a as any) + (b as any) as T; // needs cast — TS can't infer}// Go — interface constraint with type uniontype Number interface { ~int | ~int32 | ~int64 | ~float32 | ~float64}
func Sum[T Number](nums []T) T { var total T for _, n := range nums { total += n // + is valid because all listed types support it } return total}
type Celsius float64 // underlying type is float64temps := []Celsius{20, 21, 22}fmt.Println(Sum(temps)) // works — ~float64 includes Celsiusสิ่งที่ generics ของ Go ทำไม่ได้
หัวข้อที่มีชื่อว่า “สิ่งที่ generics ของ Go ทำไม่ได้”ระบบ type ของ Go ถูกออกแบบให้เรียบง่ายโดยตั้งใจ มีสามสิ่งที่นักพัฒนา TypeScript มักหยิบมาใช้แต่ไม่มีใน Go:
// TypeScript — advanced generic features Go lacks
// 1. Conditional typestype IsString<T> = T extends string ? "yes" : "no";type R1 = IsString<string>; // "yes"type R2 = IsString<number>; // "no"
// 2. Mapped typestype Optional<T> = { [K in keyof T]?: T[K] };type User = { id: number; name: string };type PartialUser = Optional<User>; // { id?: number; name?: string }
// 3. Template literal typestype EventName = `on${Capitalize<string>}`;// Go — none of these exist; alternatives:
// 1. No conditional types — use multiple functions or interfaces// Instead of IsString<T>: write two separate functions
// 2. No mapped types — use struct embedding or code generation// go generate + text/template for mechanical transformations
// 3. No template literal types — strings are just strings at runtime
// Go's philosophy: if the type system cannot express it simply,// use code generation (go generate) or accept a small amount// of repetition. Explicitness over metaprogramming.Map และ Filter แบบ generic
หัวข้อที่มีชื่อว่า “Map และ Filter แบบ generic”// TypeScript — map and filter are built into Arrayconst doubled = [1, 2, 3].map(n => n * 2);const evens = [1, 2, 3].filter(n => n % 2 === 0);// Go 1.18+ — implement with generics (not in stdlib yet)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 Filter[T any](slice []T, fn func(T) bool) []T { var result []T for _, v := range slice { if fn(v) { result = append(result, v) } } return result}
doubled := Map([]int{1,2,3}, func(n int) int { return n * 2 })evens := Filter([]int{1,2,3}, func(n int) bool { return n%2==0 })ลองเล่นดู
หัวข้อที่มีชื่อว่า “ลองเล่นดู”package main
import "fmt"
// Number constraint — ~int means any type with underlying inttype Number interface { ~int | ~float64}
// Sum works for any Number typefunc Sum[T Number](nums []T) T { var total T for _, n := range nums { total += n } return total}
// Map transforms a slice — T and U are independent type paramsfunc 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}
// Contains requires comparable constraint for ==func Contains[T comparable](slice []T, item T) bool { for _, v := range slice { if v == item { return true } } return false}
func main() { ints := []int{1, 2, 3, 4, 5} floats := []float64{1.1, 2.2, 3.3}
fmt.Println("sum ints: ", Sum(ints)) fmt.Println("sum floats:", Sum(floats))
doubled := Map(ints, func(n int) int { return n * 2 }) fmt.Println("doubled: ", doubled)
strs := Map(ints, func(n int) string { return fmt.Sprintf("#%d", n) }) fmt.Println("strings: ", strs)
fmt.Println("has 3:", Contains(ints, 3)) fmt.Println("has 9:", Contains(ints, 9))}Loading Go runtime (first run only, ~8 MB)…