ภาพสายพานสีเขียวสามช่วง แปลงข้อมูลวงกลมเป็นสี่เหลี่ยมและผลลัพธ์สีอำพัน โดยมีตัวละครคล้ายโกเฟอร์กำลังตรวจการทำงาน

Go 1.27 มี Generic Methods แล้ว: จัดสายพานแปลงข้อมูลให้อ่านจากซ้ายไปขวา

พี่โชว์Engineering

ถ้าเคยเขียน generics ใน Go แล้วสงสัยว่า “ทำไม Map ต้องเป็นฟังก์ชันลอยอยู่ใน package” คำตอบเดิมคือ method ประกาศ type parameter ของตัวเองไม่ได้ครับ แต่ Go 1.27 เปลี่ยนกติกาข้อนี้แล้ว

ผลที่ได้ไม่ใช่แค่ syntax ใหม่หนึ่งบรรทัด สิ่งสำคัญกว่าคือเราสามารถวาง operation ไว้ใกล้ type ที่มันดูแล และอ่านการแปลงข้อมูลจากซ้ายไปขวาได้เหมือนสายพานในโรงงาน แทนที่จะซ้อนฟังก์ชันจากข้างในออกมาข้างนอก

Go 1.27 ออกเมื่อ 19 สิงหาคม 2026 และทีม Go เผยบทอธิบายเรื่อง Generic Methods โดยเฉพาะเมื่อ 26 สิงหาคม 2026 บทความนี้จึงโฟกัสว่าใช้แล้วช่วยอะไร พร้อมขอบเขตที่ยังไม่ควรก้าวข้าม

WHY: ปัญหาเดิมไม่ได้อยู่ที่ทำไม่ได้ แต่อยู่ที่อ่านยากขึ้นเรื่อย ๆ

สมมติเรามี Pipeline[int] แล้วต้องแปลงเลขเป็นข้อความ ก่อนแปลงข้อความเป็นความยาว ก่อน Go 1.27 เราทำ generic function แบบ MapPipeline(pipeline, fn) ได้อยู่แล้ว แต่เมื่อแปลงหลายชั้น โค้ดมักกลายเป็นการครอบวงเล็บซ้อนกัน

นึกภาพครัวที่ส่งจานจากสถานีหนึ่งไปอีกสถานีหนึ่ง ถ้าเราอ่านคำสั่งว่า “ห่อสถานีล้างไว้ในสถานีหั่น แล้วห่อทั้งหมดไว้ในสถานีเสิร์ฟ” คนอ่านต้องแกะจากวงเล็บชั้นในก่อน แต่ method chain บอกตรง ๆ ว่า “รับวัตถุดิบ → หั่น → ปรุง → เสิร์ฟ”

Generic method ทำให้ concrete type หรือชนิดข้อมูลจริง เช่น Pipeline[T] ประกาศ type parameter เพิ่มเฉพาะ method ได้ ดังนั้น Pipeline[int] สามารถ Map ไปเป็น Pipeline[string] และต่อไปเป็น Pipeline[int] อีกครั้ง โดยแต่ละช่วงให้ compiler ตรวจชนิดข้อมูล

HOW: ตัวอย่างที่รันด้วย Go 1.27 จริง

โค้ดด้านล่างมี test ที่ตรวจทั้งจำนวนผลลัพธ์และค่าหลังแปลง ไม่ได้พิสูจน์เพียงว่า compile ผ่าน

package pipeline

import (
	"reflect"
	"strings"
	"testing"
)

type Pipeline[T any] struct {
	values []T
}

func NewPipeline[T any](values ...T) Pipeline[T] {
	return Pipeline[T]{values: values}
}

// Map มี type parameter R ของตัวเอง ซึ่งเพิ่งรองรับใน Go 1.27
func (p Pipeline[T]) Map[R any](transform func(T) R) Pipeline[R] {
	result := make([]R, 0, len(p.values))
	for _, value := range p.values {
		result = append(result, transform(value))
	}
	return Pipeline[R]{values: result}
}

func (p Pipeline[T]) Values() []T {
	return p.values
}

func TestMapChainChangesType(t *testing.T) {
	got := NewPipeline(1, 2, 3).
		Map(func(n int) string { return strings.Repeat("x", n) }).
		Map(func(text string) int { return len(text) }).
		Values()

	want := []int{1, 2, 3}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("Map chain = %v, want %v", got, want)
	}
}

จุดที่น่าสังเกตคือ T เป็นชนิดข้อมูลของ receiver ส่วน R เป็นชนิดปลายทางของการแปลงครั้งนั้น ทั้งสองไม่จำเป็นต้องเหมือนกัน Compiler จึงช่วยจับกรณีที่สถานีหนึ่งส่งวัตถุดิบผิดชนิดให้สถานีถัดไปได้ตั้งแต่ build

แล้วควรย้าย generic function ทุกตัวมาเป็น method ไหม

ไม่ควรครับ ให้ถามว่า operation นี้เป็น “พฤติกรรมตามธรรมชาติของ type” หรือเป็นเครื่องมือทั่วไปของ package

  • ถ้าคนใช้มักเริ่มจากค่า Pipeline แล้วไล่ operation ต่อกัน Map แบบ method ช่วยให้ค้น API และอ่าน flow ง่าย
  • ถ้าฟังก์ชันทำงานกับหลาย type อย่างเท่าเทียมกัน หรือไม่มี receiver ที่เป็นเจ้าของความหมายชัดเจน generic function ยังตรงกว่า
  • ถ้า method ทำให้ type มี API จำนวนมากจนหาไม่เจอ เราเพียงย้ายความรกจาก package มาไว้บน type เท่านั้น

พูดง่าย ๆ คือ method เป็นชั้นวางของ ไม่ใช่ห้องเก็บของที่ต้องยัดทุกอย่างเข้าไป

ขอบเขตสำคัญ: Generic interface methods ยังไม่มี

Release notes ของ Go 1.27 ระบุชัดว่า interface method ยังประกาศ type parameter ไม่ได้ และ generic method ของ concrete type ไม่ได้กลายเป็นวิธี implement interface method แบบ generic โดยอัตโนมัติ

เหตุผลเชิงออกแบบคือ interface ต้องรู้ method set ของ type ส่วน generic method ยังต้องถูก instantiate หรือเลือก type argument ก่อนใช้งาน ถ้าเอาสองแนวคิดนี้มาผูกกันทันที ทั้ง compiler และคนอ่านต้องตอบคำถามเพิ่มว่า implementation รูปใดมีอยู่จริงตอนไหน

ดังนั้นในงานที่ต้องการ abstraction ผ่าน interface ให้เก็บ interface เล็กและเป็น operation ที่มีชนิดแน่นอน แล้วใช้ generic method เพื่อจัดระเบียบ concrete workflow รอบนอก จะได้ประโยชน์ใหม่โดยไม่ทำให้ contract คลุมเครือ

Recap

Go 1.27 ไม่ได้ทำให้ generics กลายเป็นภาษาคนละภาษา แต่เติมชิ้นส่วนที่ขาดเพื่อให้ operation อยู่ใกล้ type และต่อสายพานการแปลงได้ตรงตามลำดับความคิด

จำไว้สามข้อ: ใช้ generic method เมื่อ receiver เป็นเจ้าของความหมาย อ่าน chain จากซ้ายไปขวา และอย่าสับสนว่ามันทำให้ generic interface methods ใช้งานได้แล้ว ถ้าทีมเริ่มจาก WHY เหล่านี้ ฟีเจอร์ใหม่จะช่วยลดภาระการอ่าน มากกว่าจะเพิ่มลูกเล่นให้ API ครับ