Map/Filter/Reduce, closure, curry ด้วย generics
แบบเดิม (imperative): สั่งทีละขั้น "ทำนี่ก่อน แล้วทำนั่น"
แบบ functional: บอกว่า "เอาของมาผ่านสายพานทีละเครื่อง"
ข้อดี: อ่านเป็นประโยค เทสต์ทีละเครื่องง่าย ไม่ต้องจำว่า total โดนแก้ตรงไหนบ้าง
Go ทำได้ ~70% ของ functional แท้: ไม่มี map/filter ใน std โดยตรง (ต้องเขียนเองหรือใช้ slices + generic) แต่ไอเดียใช้ได้ครบ
ใน Go ฟังก์ชันเก็บในตัวแปรได้เหมือนเลข:
add := func(a, b int) int { return a + b }
fmt.Println(add(2, 3)) // 5
// ส่งฟังก์ชันเข้าฟังก์ชัน
func Apply(f func(int) int, v int) int { return f(v) }
fmt.Println(Apply(func(x int) int { return x*2 }, 5)) // 10
// คืนฟังก์ชันออกมา
func Maker() func() int {
n := 0
return func() int { n++; return n }
}
c := Maker()
fmt.Println(c(), c()) // 1 2type ตั้งชื่อให้ฟังก์ชันได้ อ่านง่ายขึ้น:
เขียนเองครั้งเดียว ใช้ได้ทุก type ด้วย generics (Go 1.18+):
func Map[T, R any](s []T, f func(T) R) []R {
out := make([]R, 0, len(s))
for _, v := range s { out = append(out, f(v)) }
return out
}
func Filter[T any](s []T, keep func(T) bool) []T {
var out []T
for _, v := range s { if keep(v) { out = append(out, v) } }
return out
}
func Reduce[T, R any](s []T, init R, f func(R, T) R) R {
acc := init
for _, v := range s { acc = f(acc, v) }
return acc
}
// ใช้
nums := []int{1, 2, 3, 4}
evens := Filter(nums, func(n int) bool { return n%2 == 0 }) // [2 4]
squares := Map(evens, func(n int) int { return n*n }) // [4 16]
sum := Reduce(squares, 0, func(a, b int) int { return a+b }) // 20ทริก: ถ้าโปรเจกต์เล็กใช้ for ธรรมดาก็พอ อย่าฝืน functional จนอ่านยาก กฎ Effective Go คืออ่านง่ายก่อนเท่
Closure = ฟังก์ชันจำของข้างนอกได้:
Curry = แตกฟังก์ชันหลายช่องให้เหลือช่องเดียวต่อครั้ง:
func Add(a int) func(int) int {
return func(b int) int { return a+b }
}
add5 := Add(5)
fmt.Println(add5(3)) // 8ใช้ทำ middleware / ตั้งค่า:
func WithPrefix(p string) func(string) string {
return func(s string) string { return p + s }
}
hello := WithPrefix("สวัสดี ")
fmt.Println(hello("สมชาย"))// pure
func Add(a, b int) int { return a+b }
// ไม่ pure (แอบแก้ข้างนอก)
var total int
func AddToTotal(n int) { total += n }Go ไม่มี immutable แท้ๆ แต่เลียนแบบได้:
append([]T{}, s...) ก็อปก่อนแก้ ไม่แก้ของเดิมGo 1.27 เดาชนิดให้ ไม่ต้องเขียน [T] ย้ำ แม้ส่งฟังก์ชันเข้า channel:
func GenericFormatter[T any](v T) string { return fmt.Sprintf("value: %v", v) }
type IntFormatter func(int) string
ch := make(chan IntFormatter, 1)
ch <- GenericFormatter // 1.27 เดา T=int ให้เอง เมื่อก่อนต้องเขียน GenericFormatter[int]
formatters := []IntFormatter{GenericFormatter}เอา functional มาผสม pipeline:
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() { defer close(out); for _, n := range nums { out <- n } }()
return out
}
func MapChan[T, R any](in <-chan T, f func(T) R) <-chan R {
out := make(chan R)
go func() { defer close(out); for v := range in { out <- f(v) } }()
return out
}
// ใช้: for v := range MapChan(gen(1,2,3,4), func(n int) int { return n*n }) { fmt.Println(v) }โจทย์: มีออเดอร์ []Order{Price, Qty} ให้กรองเฉพาะ Qty>0 → คิด Price*Qty → รวมยอด ด้วย Map/Filter/Reduce ที่เขียนเอง + เทสต์
แบบฝึก:
Filter กรองเลขคี่Map แปลงชื่อเป็น "สวัสดี "+ชื่อgen -> filter คู่ -> square ด้วย channelgo vet + go fix ./... ดูว่าแนะนำอะไร (1.26 มี modernizers ช่วยย่อโค้ด)อ้างอิง: Effective Go #functions, Go 1.27 blog generic inference + channel sends
func Map[T any, U any](xs []T, f func(T) U) []U {
out := make([]U, 0, len(xs))
for _, x := range xs {
out = append(out, f(x))
}
return out
}
names := Map([]int{1, 2, 3}, func(n int) string {
return fmt.Sprint("เบอร์ ", n)
})func Filter[T any](xs []T, keep func(T) bool) []T {
var out []T
for _, x := range xs {
if keep(x) {
out = append(out, x)
}
}
return out
}
evens := Filter([]int{1, 2, 3, 4}, func(n int) bool { return n%2 == 0 })func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
a, b := counter(), counter()
fmt.Println(a(), a(), b()) // 1 2 1 (แยกกัน ไม่แย่งกัน)Reduce รวมยอดราคาสินค้าในตะกร้าretry(fn, 3) higher-order ที่ลองใหม่เองเมื่อ error