ข้ามไปยังเนื้อหา

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
// TypeScript — structural constraint
function identity<T>(value: T): T {
return value;
}
function first<T extends { length: number }>(arr: T[]): T {
return arr[0];
}
// Generic container
class Stack<T> {
private items: T[] = [];
push(item: T) { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
}
Go
// Go — type parameter + constraint interface
func Identity[T any](value T) T {
return value
}
func First[T any](slice []T) T {
return slice[0]
}
// Generic stack
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
}
last := s.items[len(s.items)-1]
s.items = s.items[:len(s.items)-1]
return last, true
}

constraint any หมายถึง “type อะไรก็ได้” — เทียบเท่า interface{} ในโลกของ generics แต่ Go ให้คุณเขียน constraint ที่ละเอียดกว่านั้นได้ ด้วย interface ที่ระบุ underlying type ผ่าน operator ~

TypeScript
// TypeScript — union type constraint
type Numeric = number | bigint;
function add<T extends Numeric>(a: T, b: T): T {
return (a as any) + (b as any) as T;
}
Go
// Go — constraint interface with type union
type Number interface {
~int | ~int32 | ~int64 | ~float64
}
func Add[T Number](a, b T) T {
return a + b
}
// Usage
x := Add(1, 2) // int
y := Add(1.5, 2.5) // float64

syntax ~int หมายถึง “type ใดก็ตามที่มี underlying type เป็น int” ดังนั้น custom type อย่าง type MyID int ก็เข้าเงื่อนไข ~int ด้วย ถ้าไม่มี ~ จะตรงเฉพาะ type int ตรง ๆ เท่านั้น

TypeScript เทียบอะไรก็ได้ด้วย === ส่วน operator == ของ Go ใช้ได้เฉพาะกับ type ที่ comparable เท่านั้น (ไม่ใช่ slice, map หรือ function) constraint comparable ที่มีมาในตัวสื่อถึงข้อจำกัดนี้:

TypeScript
// TypeScript — == works on anything
function contains<T>(arr: T[], val: T): boolean {
return arr.some(x => x === val);
}
Go
// Go — comparable constraint required for ==
func Contains[T comparable](slice []T, val T) bool {
for _, v := range slice {
if v == val {
return true
}
}
return false
}
// Works for int, string, struct with comparable fields — but NOT for slices or maps.

constraint interface รวมทั้ง method requirement และ type union ไว้ด้วยกันได้:

type Stringer interface {
String() string
}
// Accept any type that is int OR float64 AND has a String() method
// (rare but valid in library code)
type StringableNumber interface {
~int | ~float64
String() string
}

ในทางปฏิบัติ constraint ส่วนใหญ่เป็น method set ล้วน ๆ (classic interface) หรือไม่ก็ type union ล้วน ๆ (งานคำนวณตัวเลข/การเรียงลำดับ)

TypeScript
// TypeScript — stdlib has these
const doubled = [1,2,3].map(x => x * 2);
const evens = [1,2,3,4].filter(x => x % 2 === 0);
Go
// Go — before 1.21 you wrote these yourself
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
}
// Since Go 1.23 slices.Collect + iter package handles this more idiomatically

TypeScript ให้ method ประกาศ type parameter ของตัวเองได้มาตลอด (class Box { map<U>(fn: (v: T) => U) }) แต่ Go ทำไม่ได้ — จนกระทั่ง 1.27 บวกกับของเล็ก ๆ อีกสองอย่างจาก 1.26 ที่ทำให้ภาพครบ

TypeScript
// TypeScript: method-level type parameter
class Box<T> {
constructor(public value: T) {}
map<U>(fn: (v: T) => U): Box<U> {
return new Box(fn(this.value));
}
}
const n = new Box(21).map(v => v * 2); // Box<number>
const s = n.map(v => v + "!"); // Box<string>
Go
// Go 1.27+: methods may declare their own type parameters
type Box[T any] struct{ Value T }
func (b Box[T]) Map[U any](fn func(T) U) Box[U] {
return Box[U]{Value: fn(b.Value)}
}
n := Box[int]{21}.Map(func(v int) int { return v * 2 }) // Box[int]
s := n.Map(func(v int) string { return fmt.Sprint(v, "!") }) // Box[string]
// Rule: interface methods cannot declare type parameters,
// so a generic method never satisfies an interface.
// Go 1.26+: new() takes an initial value (handy for *int / *string fields)
type User struct{ Age *int }
u := User{Age: new(42)}
// Go 1.26+: a constraint may refer to the type being constrained
type Adder[A Adder[A]] interface{ Add(A) A }
func Sum[A Adder[A]](x, y A) A { return x.Add(y) }

stdlib ใช้ generic method แล้ว: (*Rand).N[Int intType](Int) ใน math/rand/v2 และเพราะ interface ใส่ generic method ไม่ได้ มันจึงเป็นแค่ความสะดวกสำหรับ concrete type — ไม่ใช่ทางไปสู่ generic interface แบบ Java

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))
}
syntax `~int` ใน constraint interface ของ Go หมายความว่าอะไร?
constraint ในตัวใดที่รับประกันว่า operator == สามารถใช้กับ type parameter ได้?
เมื่อใดควรเลือก plain interface แทน generics ใน Go?
Generics ถูกเพิ่มใน Go เวอร์ชัน: