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

Generics — Go เทียบกับ TypeScript

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

syntax คล้ายกัน: ใช้วงเล็บเหลี่ยมสำหรับ type parameters

TypeScript
// TypeScript — structural generic
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
// Works for any type — no explicit constraint needed
first([1, 2, 3]); // number
first(["a", "b"]); // string
first([{ id: 1 }]); // object
// Constrained generic
function 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
// Go — type parameter with explicit constraint
func 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 interface
type 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' constraint
func Contains[T comparable](slice []T, item T) bool {
for _, v := range slice {
if v == item { return true }
}
return false
}

constraint ใน Go เขียน syntax อยู่ใน body ของ interface คุณระบุ concrete type ด้วย | เพื่อทำ type union ได้ และ ~T หมายถึง “type ใดก็ตามที่มี underlying type เป็น T” (ครอบคลุม custom type อย่าง type MyInt int)

TypeScript
// TypeScript — conditional type for numeric union
type 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
// Go — interface constraint with type union
type 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 float64
temps := []Celsius{20, 21, 22}
fmt.Println(Sum(temps)) // works — ~float64 includes Celsius

ระบบ type ของ Go ถูกออกแบบให้เรียบง่ายโดยตั้งใจ มีสามสิ่งที่นักพัฒนา TypeScript มักหยิบมาใช้แต่ไม่มีใน Go:

TypeScript
// TypeScript — advanced generic features Go lacks
// 1. Conditional types
type IsString<T> = T extends string ? "yes" : "no";
type R1 = IsString<string>; // "yes"
type R2 = IsString<number>; // "no"
// 2. Mapped types
type 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 types
type EventName = `on${Capitalize<string>}`;
Go
// 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.
TypeScript
// TypeScript — map and filter are built into Array
const doubled = [1, 2, 3].map(n => n * 2);
const evens = [1, 2, 3].filter(n => n % 2 === 0);
Go
// 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 int
type Number interface {
~int | ~float64
}
// Sum works for any Number type
func 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 params
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
}
// 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))
}
constraint ใดที่อนุญาตให้ใช้ == ระหว่างค่าสองค่าของ generic type ใน Go?
~int ใน type constraint ของ Go หมายความว่าอย่างไร?
ฟีเจอร์ generic ของ TypeScript ข้อใดที่ Go ไม่มี?
ใน generics ของ Go "any" ในฐานะ constraint หมายความว่าอย่างไร?