Skip to content

บทที่ 3 — Functions

← บทที่ 2 | สารบัญ | บทที่ 4 →

หลังจบบท คุณจะ:

  • เขียน function ตั้งแต่พื้นฐาน (basic) ถึงขั้นสูง (advanced)
  • ใช้ multiple return (คืนค่าหลายค่า), named return (คืนค่าแบบตั้งชื่อ)
  • เข้าใจ variadic (รับ argument ไม่จำกัดจำนวน) + closure (ฟังก์ชันที่จำตัวแปรรอบตัวได้) + function as value (มองฟังก์ชันเป็นค่า)
  • รู้จัก method (เมธอด — ดูพอเข้าใจรอบนี้ รายละเอียดเต็มอยู่บท 5)

1. Function Basic

ฟังก์ชันคือหน่วยพื้นฐานที่รวมโค้ดไว้ใช้ซ้ำ Go ใช้คีย์เวิร์ด func ตามด้วยชื่อ, พารามิเตอร์ (ระบุ type ไว้หลังชื่อ), และ type ที่คืนค่า — ถ้าพารามิเตอร์หลายตัว type เดียวกัน ย่อรวมได้ และถ้าไม่คืนค่าก็ไม่ต้องระบุ return type:

go
func add(a int, b int) int {
    return a + b
}

// Call
sum := add(3, 5)    // 8
text
ส่วนประกอบ:
func name(params) returnType { body }

Same-Type Parameters

go
// Shorthand
func add(a, b int) int {
    return a + b
}

// Same as
func add(a int, b int) int {
    return a + b
}

No Return

go
func greet(name string) {
    fmt.Println("Hello,", name)
}

→ ไม่ระบุ returnType


2. Multiple Return Values

⭐ Feature เด่นของ Go — function return หลายค่าได้ (ต่างจาก C#: C# ก็คืนหลายค่าได้เหมือนกันด้วย ValueTuple เช่น (int, string) หรือ out parameter — แต่ใน Go multiple return คู่กับ error คืออิดิออม (idiom) หลักของทั้งภาษา ไม่ใช่แค่ทางเลือกแบบ C#)

go
import "errors" // ต้อง import เพื่อใช้ errors.New

func divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// Call
result, err := divide(10, 3)
if err != nil {
    fmt.Println("Error:", err)
    return
}
fmt.Println("Result:", result)

Common Pattern: Value + Error

go
file, err := os.Open("data.txt")
if err != nil {
    return err
}

user, err := repo.FindById(id)
if err != nil {
    return err
}

→ Go = "error เป็น return value" — ไม่ใช่ exception (กลไกโยน-ดักข้อผิดพลาดของภาษาอื่น เช่น try-catch ของ Java/Python/C#)
สำหรับ dev .NET: นี่คือการปรับ mindset ที่ใหญ่สุดตอนย้ายมา Go — เลิกใช้ throw/try-catch สำหรับ flow ปกติ ทุกฟังก์ชันที่พลาดได้จะคืน error เป็นค่าสุดท้ายเสมอ ต้องเช็ค if err != nil เองทุกจุดที่เรียก ไม่มีใครดักให้อัตโนมัติเหมือน exception

Ignore with _

go
_, err := fn()       // ignore value
result, _ := fn()    // ignore error (ระวัง!)

3. Named Return Values

go
func divide(a, b float64) (result float64, err error) {
    if b == 0 {
        err = errors.New("division by zero")
        return    // "naked return" (รีเทิร์นเปล่า — `return` ไม่ระบุค่า Go ใช้ค่าใน named return variable ให้)
    }
    result = a / b
    return       // = return result, err
}

Pros / Cons

✅ Documentation — ชื่อบอก meaning
✅ Auto zero value
❌ "Naked return" อ่านยากใน function ยาว — confusing
❌ Easy to forget assigning before return

Recommendation: ใช้กับ small function เท่านั้น — ส่วนใหญ่ explicit return ดีกว่า


4. Variadic Function

รับ argument จำนวนไม่จำกัด:

go
func sum(nums ...int) int {
    total := 0
    for _, n := range nums {
        total += n
    }
    return total
}

// Call
sum()                    // 0
sum(1)                   // 1
sum(1, 2, 3)             // 6
sum(1, 2, 3, 4, 5)        // 15

// Pass slice
nums := []int{1, 2, 3}
sum(nums...)              // spread operator (ตัวกระจาย — ใส่ `...` ต่อท้าย slice เพื่อ "แตก" สมาชิกออกเป็น argument หลายตัวให้ variadic function รับ)

Built-in Examples

go
fmt.Println("a", "b", "c")
append(slice, 1, 2, 3)
strings.Join([]string{"a", "b"}, ",")

Mixed Parameters

go
// any = interface{} (อินเทอร์เฟซว่าง — ชนิดที่รับค่าอะไรก็ได้ คล้าย Object ใน Java)
// ตั้งแต่ Go 1.18 นิยมเขียน any แทน interface{} — บท 6 อธิบายเต็ม
func logf(prefix string, args ...any) {
    fmt.Print(prefix, ": ")
    fmt.Println(args...)
}

logf("INFO", "User", 42, "logged in")

→ Variadic ต้องเป็น parameter สุดท้าย


5. ฟังก์ชันเป็นค่า (Function as Value)

ใน Go ฟังก์ชันเป็น "first-class value" (ค่าระดับเดียวกับข้อมูลทั่วไป เช่น int หรือ string) หมายความว่ามันถูกปฏิบัติเหมือนค่าทั่วไป:

  • เก็บใส่ตัวแปรได้
  • ส่งเป็น argument ให้ฟังก์ชันอื่นได้
  • ให้ฟังก์ชันคืนค่าเป็นฟังก์ชันได้
  • ใส่ใน slice/map ได้

ความสามารถนี้คือรากฐานของการเขียนโค้ดแบบ functional และ callback:

go
// ตัวอย่างด้านล่างเป็นการอธิบาย concept เท่านั้น ไม่ใช่ไฟล์เดียวรันได้ทั้งบล็อก
// apply และ multiplier เป็น "func ระดับ package" (ประกาศนอก main — ห้ามซ้อน func มีชื่อไว้ใน func อื่น
// Go อนุญาตแค่ func ไม่มีชื่อ/anonymous ซ้อนได้เท่านั้น) ส่วนที่เหลือ (add, op, result, double, triple)
// ใช้ `:=` ได้เพราะอยู่ใน func main() เท่านั้น (ตามกฎบท 1 ว่า `:=` ใช้ได้เฉพาะในฟังก์ชัน)

// --- ระดับ package (นอก main) ---
func apply(fn func(int, int) int, a, b int) int {
    return fn(a, b)
}

func multiplier(factor int) func(int) int {
    return func(x int) int {
        return x * factor
    }
}

// --- ใน func main() ---
// Assign
add := func(a, b int) int { return a + b }   // นิยาม add ก่อน
var op func(int, int) int = add
op = func(a, b int) int { return a - b }   // anonymous function

result := op(10, 3)    // 7

// Pass as argument
apply(add, 3, 5)    // 8
apply(func(a, b int) int { return a * b }, 3, 5)    // 15

// Return from function
double := multiplier(2)
double(5)    // 10
double(10)   // 20

triple := multiplier(3)
triple(5)    // 15

6. Closure — Capture Variable

Closure (โคลเชอร์) = anonymous function (ฟังก์ชันไม่มีชื่อ) ที่ "จำ" ตัวแปรนอก scope (ขอบเขตของตัวมัน) ได้ — เหมือน lambda ของ C# ที่ capture ตัวแปรรอบข้างได้เช่นกัน (แต่ระวัง loop variable capture ด้านล่าง: Go < 1.22 พังแบบเดียวกับที่ C# foreach เคยพังก่อนแก้ในเวอร์ชันเก่า):

go
func counter() func() int {
    count := 0
    return func() int {
        count++
        return count
    }
}

c1 := counter()
c1()    // 1
c1()    // 2
c1()    // 3

c2 := counter()    // separate count
c2()    // 1
c2()    // 2

count อยู่ใน counter() แต่ inner function "จำ" และ modify ได้ — แม้ outer return ไปแล้ว

Use Case

go
// Filter
func makeFilter(predicate func(int) bool) func([]int) []int {
    return func(nums []int) []int {
        var result []int
        for _, n := range nums {
            if predicate(n) {
                result = append(result, n)
            }
        }
        return result
    }
}

positive := makeFilter(func(n int) bool { return n > 0 })
even := makeFilter(func(n int) bool { return n%2 == 0 })

positive([]int{-1, 2, -3, 4})    // [2 4]
even([]int{1, 2, 3, 4, 5})        // [2 4]

Closure Gotcha — Loop Variable

go
// Go < 1.22 problem (Go 1.22+ fixed)
funcs := []func(){}
for i := 0; i < 3; i++ {
    funcs = append(funcs, func() { fmt.Println(i) })
}

// Go < 1.22: prints 3 3 3 (all share same i)
// Go >= 1.22: prints 0 1 2 (per-iteration i)

7. Anonymous Function

"Anonymous function" คือฟังก์ชันที่ไม่มีชื่อ — เขียนตรงจุดที่ใช้เลย จะเรียกทันที (IIFE), เก็บใส่ตัวแปร, หรือส่งเป็น argument ก็ได้ พบบ่อยมากเวลาใช้กับ goroutine, defer และ callback:

go
// Immediately Invoked
func() {
    fmt.Println("anonymous")
}()       // ()  ← call immediately

// With argument
func(name string) {
    fmt.Println("Hi", name)
}("Anna")

// Assign + call later
greet := func(name string) {
    fmt.Println("Hi", name)
}
greet("Anna")
greet("Ben")

ใช้กับ:

  • Goroutine launch
  • defer
  • Callback

8. Recursive Function

ฟังก์ชัน recursive (เรียกซ้ำ/เรียกตัวเอง) คือฟังก์ชันที่เรียกตัวเอง — เหมาะกับปัญหาที่แตกเป็นปัญหาย่อยแบบเดียวกัน เช่น factorial (แฟกทอเรียล n!).

ข้อควรระวังใน Go คือ ถ้า recursion ลึกมากอาจเกิด stack overflow (กองซ้อนการเรียกฟังก์ชันล้น จนโปรแกรมพัง) เหตุผลคือ Go ไม่มี tail-call optimization (การปรับโค้ดให้เรียกซ้ำท้ายฟังก์ชันไม่กิน stack เพิ่ม) — ถ้าเรียกตัวเองลึกมากทุก frame จะถูกเก็บสะสมไว้จริง ๆ

อย่างไรก็ตาม Go ช่วยผ่อนไว้บางส่วน: stack ของแต่ละ goroutine เริ่มเล็กแล้วขยายได้อัตโนมัติ (dynamic stack growth) ทำให้ recursion ระดับลึกปานกลางปลอดภัยตามปกติ แต่ถ้าลึกมาก ๆ จริง (ระดับหลักล้าน frame) ก็ยังอาจ stack overflow ได้อยู่ดี ดังนั้นงานที่ต้องวนซ้ำลึกมาก บางครั้งเขียนแบบ loop จะปลอดภัยกว่า:

go
func factorial(n int) int {
    if n <= 1 {
        return 1
    }
    return n * factorial(n - 1)
}

factorial(5)    // 120

⚠️ ระวัง stack overflow ที่ recursion ลึกมาก — Go ไม่มี tail-call optimization

go
// Better: iterative
func factorial(n int) int {
    result := 1
    for i := 2; i <= n; i++ {
        result *= i
    }
    return result
}

9. Pass by Value (Always)

Go = always pass by value — แต่บางอย่างที่ "value" คือ reference อยู่แล้ว

⚠️ ระวัง! false friend กับ C#: ใน C# ส่ง class object เข้าฟังก์ชันแล้วแก้ field ข้างในได้เลย (เพราะ class = reference type) แต่ Go struct ส่งเข้าฟังก์ชันแล้วได้แค่ "สำเนา" แก้เท่าไหร่ก็ไม่ติดกลับไปที่ตัวเดิม — พูดง่าย ๆ Go struct ≈ C# struct ไม่ใช่ C# class ถ้าอยากแก้ของจริงต้องส่ง pointer (*User) แบบด้านล่าง

Primitive — Copy

go
func increment(n int) {
    n++       // local copy
}

x := 5
increment(x)
fmt.Println(x)    // 5 (unchanged)

Struct — Copy

go
type User struct {
    Name string
    Age  int
}

func updateUser(u User) {
    u.Name = "Modified"    // local copy
}

user := User{Name: "Anna"}
updateUser(user)
fmt.Println(user.Name)    // Anna (unchanged)

Pointer — "By reference" (effectively)

go
func updateUser(u *User) {
    u.Name = "Modified"
}

user := User{Name: "Anna"}
updateUser(&user)
fmt.Println(user.Name)    // Modified

Slice, Map, Channel — "By reference" (technically by value, but value = pointer)

go
func modifySlice(s []int) {
    s[0] = 999    // modify underlying array (อาเรย์ภายใน — เดี๋ยวบท 4 อธิบายเต็ม)
}

nums := []int{1, 2, 3}
modifySlice(nums)
fmt.Println(nums)    // [999 2 3]

→ Slice ภายในคือกลุ่ม 3 ข้อมูล: pointer ที่ชี้ไป array จริง + len (จำนวนที่ใช้อยู่) + cap (จำนวนที่จองไว้). ตอนส่งเข้าฟังก์ชัน Go ก๊อปเฉพาะ 3 ข้อมูลนี้ แต่ pointer ยังชี้ array ก้อนเดิม.
→ รายละเอียดเต็มของ slice + backing array (อาเรย์ภายในของ slice) เดี๋ยวบท 4 อธิบาย — ตอนนี้แค่รู้ว่า "slice ส่งเข้าฟังก์ชันแล้วแก้ค่าจริงได้" ก็พอ


10. Defer + Function — Common Pattern

แพตเทิร์นที่ใช้บ่อยที่สุดของ defer คือเก็บกวาดทรัพยากรหลังเปิด เช่นเปิดไฟล์แล้ว defer file.Close() ทันที — มั่นใจว่าปิดเสมอไม่ว่าฟังก์ชันจะ return ทางไหน:

go
func processFile(path string) error {
    file, err := os.Open(path)
    if err != nil {
        return err
    }
    defer file.Close()      // cleanup, no matter how function exits
    
    return doWork(file)
}

Defer in Loop (อันตราย)

go
// ❌ Resource leak — defer accumulate (และอย่าใช้ _ ละเลย error ในโค้ดจริง)
for _, path := range files {
    file, _ := os.Open(path)   // ⚠️ ตัวอย่างละ error ไว้เพื่อโฟกัสบั๊ก defer
    defer file.Close()         // ทุกไฟล์ defer — close ตอน function จบ
    // ...
}
// 1000 files → 1000 open handles!

// ✅ Use function (พร้อม error handling เต็ม)
for _, path := range files {
    func() {
        file, err := os.Open(path)
        if err != nil {
            log.Printf("open %s: %v", path, err)
            return
        }
        defer file.Close()
        // ...
    }()
}

11. Method (Preview)

นอกจากฟังก์ชันลอย ๆ Go ยังมี "method" คือฟังก์ชันที่ผูกกับ type หนึ่ง (เขียน receiver ไว้หน้าชื่อ เช่น func (r Rectangle) Area()) ทำให้เรียกแบบ r.Area() ได้ — ตรงนี้แค่เกริ่นให้เห็นภาพ รายละเอียดเต็มอยู่บทที่ 5:

Method = function ที่ผูกกับ type:

go
type Rectangle struct {
    Width, Height float64
}

// Method on Rectangle
func (r Rectangle) Area() float64 {
    return r.Width * r.Height
}

// Call
r := Rectangle{Width: 3, Height: 4}
r.Area()    // 12

→ รายละเอียด: บทที่ 5 (Structs + Methods)


12. Function Type

เราตั้งชื่อให้ "รูปแบบของฟังก์ชัน" (signature) ได้ด้วย type เช่น type BinaryOp func(int, int) int — ช่วยให้โค้ดอ่านง่ายและสื่อเจตนา ฟังก์ชันใดก็ตามที่มี signature ตรงกันก็ส่งเข้าไปใช้ได้ เจอบ่อยใน strategy pattern, callback และ HTTP handler:

go
// Define a function type
type BinaryOp func(int, int) int

// Use as parameter type
func calculate(a, b int, op BinaryOp) int {
    return op(a, b)
}

// Functions ที่ match signature ก็ใช้ได้
add := func(a, b int) int { return a + b }
mul := func(a, b int) int { return a * b }

calculate(3, 5, add)    // 8
calculate(3, 5, mul)    // 15

ใช้สำหรับ:

  • Strategy pattern
  • Callback
  • HTTP handler
go
// http.ResponseWriter + *http.Request มาจาก package net/http ของ stdlib
// ใช้รับ-ตอบ HTTP request — บท 11 (Standard Library) และบท 13 อธิบายเต็ม
type Handler func(http.ResponseWriter, *http.Request)

func middleware(next Handler) Handler {
    return func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Request: %s", r.URL.Path)
        next(w, r)
    }
}

💡 ในงานจริง stdlib มี http.HandlerFunc (ที่ signature เดียวกันนี้) ให้แล้ว — ตัวอย่างด้านบนเขียน Handler เองเพื่อสอนเรื่อง "function type" เท่านั้น


13. Recover from Panic

panic คือการที่โปรแกรมเจอสถานการณ์ร้ายแรงแล้วหยุดทำงาน ส่วน recover (เรียกใน defer) ช่วย "ดักจับ" panic นั้นไม่ให้โปรแกรมล่มทั้งตัว — เป็นระบบคล้าย exception ของภาษาอื่น แต่ Go แนะนำให้ใช้น้อย ใช้ error สำหรับ business logic ปกติ เก็บ panic ไว้สำหรับกรณีที่กู้ไม่ได้จริง ๆ:

go
func safeDivide(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("recovered: %v", r)
        }
    }()
    
    return a / b, nil    // panic if b == 0
}

result, err := safeDivide(10, 0)
// result = 0, err = "recovered: integer divide by zero"

⚠️ panic + recover คือระบบจัดการ error ร้ายแรงของ Go (คล้าย try-catch ของภาษาอื่น) — แต่ Go แนะนำให้ใช้น้อย ใช้ error ปกติแทน
→ ใช้ error สำหรับ business logic, panic สำหรับ unrecoverable (กรณีกู้ไม่ได้จริง ๆ)


14. init() Function

go
package mypackage

import "fmt"

var config Config

func init() {
    config = loadConfig()
    fmt.Println("Package initialized")
}

init() ถูก auto-call ก่อน main() — ก่อน package ใด ๆ จะใช้ (ต่างจาก C#: คล้าย static constructor ที่รันครั้งเดียวก่อนใช้ class — แต่ Go มีได้หลายตัวต่อ package และรันตามลำดับ import)

  • มีได้หลาย init() ใน package เดียว
  • ใช้สำหรับ register driver (ลงทะเบียน driver — เช่น database/sql driver ตอน startup), validate config (ตรวจสอบไฟล์ config ก่อนเริ่มทำงาน)

⚠️ ใช้น้อย — ส่วนใหญ่ explicit constructor / dependency injection ดีกว่า


15. Documentation Comments

Go ให้ความสำคัญกับเอกสารในโค้ด — คอมเมนต์ที่เขียนเหนือ function/package (ขึ้นต้นด้วยชื่อสิ่งนั้น) จะถูกดึงเป็นเอกสารอัตโนมัติด้วย go doc และแสดงบน pkg.go.dev โดยไม่ต้องเขียนเอกสารแยก:

go
// Package math provides basic math functions.
package math

// Add returns the sum of a and b.
//
// Example:
//
//   sum := Add(3, 5)  // 8
func Add(a, b int) int {
    return a + b
}
bash
go doc math.Add

→ Comment ก่อน function = doc comment (gofmt มาตรฐาน)


16. ตัวอย่างเต็ม — เครื่องคิดเลข (Function as Value)

รวมแนวคิดทั้งบท — function type, function as value, closure, map เก็บฟังก์ชัน — ในโปรแกรมที่รันได้และอ่านง่าย:

go
package main

import (
    "errors"
    "fmt"
)

// ชนิดของ operation (function type)
type MathOp func(float64, float64) (float64, error)

// ลงทะเบียน operations ใน map
func makeOperations() map[string]MathOp {
    return map[string]MathOp{
        "+": func(a, b float64) (float64, error) { return a + b, nil },
        "-": func(a, b float64) (float64, error) { return a - b, nil },
        "*": func(a, b float64) (float64, error) { return a * b, nil },
        "/": func(a, b float64) (float64, error) {
            if b == 0 {
                return 0, errors.New("หารด้วยศูนย์ไม่ได้")
            }
            return a / b, nil
        },
    }
}

// calculator รับ op เป็น function — ใช้ function as argument
func calculate(a, b float64, op MathOp) (float64, error) {
    return op(a, b)
}

func main() {
    ops := makeOperations()

    pairs := [][3]float64{
        {10, 3, 0}, // จะเลือก op ตามลูป
    }
    _ = pairs // ตัวอย่างเรียกตรง ๆ

    for symbol, op := range ops {
        result, err := calculate(10, 3, op)
        if err != nil {
            fmt.Printf("10 %s 3 = error: %v\n", symbol, err)
        } else {
            fmt.Printf("10 %s 3 = %.4g\n", symbol, result)
        }
    }

    // closure: สร้าง "multiplier" ที่จำ factor ไว้
    double := func(x float64) float64 { return x * 2 }
    triple := func(x float64) float64 { return x * 3 }

    fmt.Println("double(5) =", double(5))
    fmt.Println("triple(5) =", triple(5))
}

→ Demonstrate: function type, function as value, closure, map เก็บฟังก์ชัน, error handling

⚠️ ตัวอย่างขั้นสูง — Mini HTTP Router (ข้ามได้ในรอบแรก)

⚠️ ตัวอย่างนี้ กระโดดไปใช้ของบทถัดไป: struct + method (บท 5) และ net/http (บท 11/13). ถ้ายังไม่คุ้น ข้ามไปก่อนได้เลย — ค่อยกลับมาอ่านหลังเรียนบท 5

go
package main

import (
    "fmt"
    "log"
    "net/http"
)

type Handler func(http.ResponseWriter, *http.Request)

type Router struct {
    routes map[string]Handler
}

func NewRouter() *Router {
    return &Router{routes: make(map[string]Handler)}
}

func (r *Router) Handle(path string, handler Handler) {
    r.routes[path] = handler
}

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    handler, ok := r.routes[req.URL.Path]
    if !ok {
        http.NotFound(w, req)
        return
    }
    handler(w, req)
}

// Middleware
func withLogging(next Handler) Handler {
    return func(w http.ResponseWriter, r *http.Request) {
        fmt.Printf("%s %s\n", r.Method, r.URL.Path)
        next(w, r)
    }
}

func main() {
    r := NewRouter()

    r.Handle("/", withLogging(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Welcome!")
    }))

    r.Handle("/hello", withLogging(func(w http.ResponseWriter, r *http.Request) {
        name := r.URL.Query().Get("name")
        if name == "" {
            name = "stranger"
        }
        fmt.Fprintf(w, "Hello, %s!\n", name)
    }))

    // โค้ดจริงต้องเช็ค error ของ ListenAndServe ด้วย — port ชนกันจะเงียบ
    log.Fatal(http.ListenAndServe(":8080", r))
}

→ Demonstrate: function as value, closure, method, type definition


17. Common Patterns

Option Pattern (Functional Options)

💡 ตัวอย่างนี้ใช้ time.Duration (ชนิดเวลาของ Go — 30 * time.Second = 30 วินาที) ดูเต็ม ๆ บท 11

go
type Server struct {
    addr    string
    timeout time.Duration
    tls     bool
}

type Option func(*Server)

func WithAddr(addr string) Option {
    return func(s *Server) { s.addr = addr }
}

func WithTimeout(d time.Duration) Option {
    return func(s *Server) { s.timeout = d }
}

func WithTLS() Option {
    return func(s *Server) { s.tls = true }
}

func NewServer(opts ...Option) *Server {
    s := &Server{
        addr:    ":8080",         // defaults
        timeout: 30 * time.Second,
    }
    for _, opt := range opts {
        opt(s)
    }
    return s
}

// Usage
server := NewServer(
    WithAddr(":9000"),
    WithTimeout(60*time.Second),
    WithTLS(),
)

→ Pattern หลักของ Go สำหรับ configurable constructor — ดีกว่า builder pattern ของ Java

Functional Helpers (use closure)

go
// Map
func Map(s []int, fn func(int) int) []int {
    result := make([]int, len(s))
    for i, v := range s {
        result[i] = fn(v)
    }
    return result
}

// Filter
func Filter(s []int, fn func(int) bool) []int {
    var result []int
    for _, v := range s {
        if fn(v) {
            result = append(result, v)
        }
    }
    return result
}

doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 })
positive := Filter([]int{-1, 2, -3, 4}, func(n int) bool { return n > 0 })

→ Go 1.21+ มี slices package ใน stdlib ที่ให้ helper พื้นฐาน (Contains, Sort, Index, Clone, ฯลฯ) — แต่ Map/Filter/Reduce แบบ functional ยังไม่อยู่ใน stdlib (ทีม Go มีข้อเสนอ — proposal — จะเพิ่มมานานแต่ยังไม่ผ่าน) ถ้าจำเป็นให้เขียนเอง หรือใช้ไลบรารีนอก เช่น samber/lo

📦 การใช้ไลบรารีนอก (เช่น samber/lo) ต้องผ่าน go get ที่ใช้ Go Modules — รันคำสั่ง go get github.com/samber/lo แล้ว import ในโค้ด ถ้ายังไม่ได้อ่าน บท 9 — Packages + Modules ข้ามไปก่อน เขียน loop เองพอ


18. ⚠️ Common Pitfalls

ตารางนี้รวมข้อผิดพลาดเรื่องฟังก์ชันที่มือใหม่มักพลาด คู่กับวิธีที่ถูกต้อง — โดยเฉพาะ defer ใน loop ยาว, การสับสน & กับ * และความเข้าใจผิดว่า slice ถูกก๊อปเมื่อส่งเข้าฟังก์ชัน:

Forget return (ลืม return)compile error
Ignore error with _ (กลืน error)handle properly (จัดการให้เรียบร้อย)
defer in long loop (defer ใน loop ยาว)inner function or explicit close (ใช้ฟังก์ชันย่อยห่อ หรือปิดเอง)
Multiple _ in return — confusing (มี _ หลายตัว อ่านยาก)use named return (ใช้ named return)
Mix up & (address) and * (deref)& get pointer, * get value
Modify slice in function thinking it copy (คิดว่า slice ถูกก๊อป)slice = reference under header (ภายในแชร์ array เดิม)
Long function (ฟังก์ชันยาวเกินไป)extract helper functions (แยกฟังก์ชันย่อย)
Too many parameters (พารามิเตอร์เยอะเกิน)use struct or option pattern (รวมเป็น struct หรือใช้ option pattern)

19. Best Practices

สรุปแนวทางเขียนฟังก์ชันที่ดีใน Go — ให้ฟังก์ชันสั้น ทำหน้าที่เดียว คืน error อย่างชัดเจน ใช้ closure เมื่อต้องการเก็บ state และใช้ functional options เมื่อ constructor มีตัวเลือกเยอะ:

text
✅ Short function (< 50 line ideal)
✅ Single responsibility
✅ Return error explicitly
✅ Use closure for stateful behavior
✅ Function type for callback
✅ Functional options for configurable constructor
✅ Defer for cleanup
✅ Document exported function (PascalCase)

20. Checkpoint

🛠️ Checkpoint 3.1 — Math Library (ไลบรารีคณิตศาสตร์)
เขียน function:

  • Add(a, b), Sub, Mul, Div (คืนค่า value + error เมื่อหารด้วย 0)
  • Sum(nums ...int) int
  • Avg(nums ...float64) (float64, error) (หาค่าเฉลี่ย)
  • ทดสอบทุก function

🛠️ Checkpoint 3.2 — Counter Closure (ตัวนับด้วย closure)
ทำ:

  • NewCounter() คืนค่าเป็น function ที่นับ +1 ทุกครั้งที่เรียก
  • NewLimitedCounter(max) หยุดเมื่อถึง max แล้วคืน error
  • ทดสอบว่าตัวนับแต่ละตัวแยก state (สถานะ/ค่าที่จำไว้) กันได้

🛠️ Checkpoint 3.3 — Functional Helpers (ฟังก์ชันช่วยสไตล์ functional)
เขียน:

  • Map([]int, func(int) int) []int (แปลงทุกตัว)
  • Filter([]int, func(int) bool) []int (กรองเอาเฉพาะที่ผ่านเงื่อนไข)
  • Reduce([]int, func(acc, n int) int, initial int) int (ยุบรวมเป็นค่าเดียว)
  • ลองใช้ทดสอบ: คูณสอง, เอาเฉพาะเลขคู่, หาผลรวม

🛠️ Checkpoint 3.4 — Option Pattern (รูปแบบส่งตัวเลือกผ่านฟังก์ชัน)
สร้าง Logger struct:

  • WithLevel(level) — ตั้งระดับ log
  • WithOutput(writer) — ตั้งปลายทางที่จะเขียน
  • WithPrefix(prefix) — ตั้งคำนำหน้าข้อความ
  • Constructor (ฟังก์ชันสร้าง object): NewLogger(opts ...Option) *Logger

🛠️ Checkpoint 3.5 — Recover (กู้จาก panic)
เขียน function ที่ panic + recover:

  • คืน error แทนที่จะ panic
  • ทดสอบว่าผู้เรียก (caller) ได้รับ error ตามปกติ

21. สรุปบท

✅ Function: func name(params) returnType { body }
Multiple return — Go feature เด่น: return value, error
Named return — readable แต่ระวังใน function ยาว
Naked returnreturn ไม่ระบุค่า (ใช้ค่าใน named return variable) ระวังใน function ยาว
Variadic fn(args ...int) — รับ argument ไม่จำกัด
First-class function — assign, pass, return ได้
Closure — function จำตัวแปรนอก scope
Pass by value always — แต่ slice/map = reference under
Defer สำหรับ cleanup — LIFO, ระวัง defer ใน loop
Functional Options — pattern สำหรับ configurable constructor
✅ Document exported function — gofmt + go doc


← บทที่ 2 | บทที่ 4 → Collections (Array, Slice, Map)