1 พื้นฐานของฟังก์ชั่นไม่ระบุชื่อ

1.1 การบรรยายทฤษฎีเกี่ยวกับฟังก์ชั่นที่ไม่ระบุชื่อ

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

1.2 การนิยามและการใช้อย่างฟังก์ชันไม่ระบุชื่อ

ในภาษา Go นิยามพื้นฐานสำหรับการกำหนดฟังก์ชันที่ไม่ระบุชื่อคือดังนี้:

func(อาร์กิวเมนต์) {
    // ร่างของฟังก์ชัน
}

การใช้งานของฟังก์ชันไม่ระบุชื่อสามารถแบ่งออกเป็นกรณีต่าง ๆ: การกำหนดให้ตัวแปร หรือการดำเนินการโดยตรง

  • กำหนดให้ตัวแปร:
ผลรวม := func(ก int, ข int) int {
    return ก + ข
}

ผลลัพธ์ := ผลรวม(3, 4)
fmt.Println(ผลลัพธ์) // ผลลัพธ์: 7

ในตัวอย่างนี้ ฟังก์ชันที่ไม่ระบุชื่อถูกกำหนดให้กับตัวแปร ผลรวม และจากนั้นเราเรียกใช้ ผลรวม เหมือนกับฟังก์ชันทั่วไป

  • การดำเนินการโดยตรง (เรียกว่าฟังก์ชันที่ไม่ระบุชื่อแล้วดำเนินการด้วยตนเอง):
func(ก int, ข int) {
    fmt.Println(ก + ข)
}(3, 4) // ผลลัพธ์: 7

ในตัวอย่างนี้ ฟังก์ชันที่ไม่ระบุชื่อถูกดำเนินการทันทีหลังจากกำหนด โดยไม่ต้องจ่ายให้ตัวแปรทำ

1.3 ตัวอย่างการประยุกต์ใช้งานของฟังก์ชันไม่ระบุชื่อ

ฟังก์ชันที่ไม่ระบุชื่อถูกใช้ในภาษา Go อย่างแพร่หลาย และนี่คือสามารถใช้งานร่วมกัน:

  • เป็นฟังก์ชัน callback: ฟังก์ชันที่ไม่ระบุชื่อถูกใช้เป็นทำให้แล้วใช้เป็นการแจ้งเรียกบ่อยครั้ง ตัวอย่างเช่น เมื่อฟังก์ชันยอมรับฟังก์ชันอีกอันเป็นพารามิเตอร์ คุณสามารถถ่ายพารามิเตอร์เป็นฟังก์ชันที่ไม่ระบุชื่อ
func traverse(numbers []int, callback func(int)) {
    for _, num := range numbers {
        callback(num)
    }
}

traverse([]int{1, 2, 3}, func(n int) {
    fmt.Println(n * n)
})

ในตัวอย่างนี้ ฟังก์ชันที่ไม่ระบุชื่อถูกส่งเป็นพารามิเตอร์ callback ไปยัง traverse และทุกตัวเลขถูกพิมพ์เมื่อถูกยกกให้กำลังสองให้

  • สำหรับการแจ้งเรียกงานอย่างเร็วแบบทันที: บางทีเราต้องการให้ฟังก์ชันถูกแจ้งเรียกเพียงครั้งเดียวและจุดการแจ้งเรียกอยู่ใกล้ ฟังก์ชันที่ไม่ระบุชื่อสามารถถูกแจ้งเรียกทันทีเพื่อตอบสนองความต้องการนี้และลดการทำให้โค้ดซ้ำซาก
func main() {
    // ...โค้ดอื่น ๆ...

    // บล็อกโค้ดที่ต้องถูกเรียกทันที
    func() {
        // โค้ดสำหรับการประมวลผลงานตาม
        fmt.Println("ฟังก์ชันไม่ระบุชื่อทันทีถูกแจ้งเรียก")
    }()
}

ที่นี่ ฟังก์ชันไม่ระบุชื่อถูกแจ้งเรียกทันทีหลังจากที่ถูกหากุัน ใช้เพื่อให้การประมวลผลงานเล็ก ๆ น้อย ๆ โดยไม่จำเป็นต้องกำหนดฟังก์ชันภายนอก

  • การปิดแลค: ฟังก์ชันที่ไม่ระบุชื่อถูกใช้งานโดยทั่วไปในการสร้างการปิดแลคเพราะพวกเขาสามารถรับตัวแปรภายนอก
func sequenceGenerator() func() int {
    i := 0
    return func() int {
        i++
        return i
    }
}

ในตัวอย่างนี้ sequenceGenerator คืนฟังก์ชันที่ไม่ระบุชื่อที่ปิดเกี่ยวกับตัวแปร i และทุกครั้งที่เรียกจะเพิ่ม i

ถึงแม้ว่าความยืดหยุ่ของฟังก์ชันที่ไม่ระบุชื่อนั้นสำคัญในการเขียนโปรแกรมจริง ๆ การเรียบง่ายโค้ดและความอ่านง่าย ในส่วนถัดไปเราจะพูดถึงการปิดแลคอย่างละเอียดรวมถึงลักษณะและการประยุกต์ใช้งานของมันอีกแพ็คหน้า

2.2 ความสัมพันธ์กับฟังก์ชันไม่ระบุชื่อ

ฟังก์ชันไม่ระบุชื่อและการปิด (closures) สัมพันธ์กันอย่างใกล้ชิด ในภาษา Go ฟังก์ชันไม่ระบุชื่อคือฟังก์ชันที่ไม่มีชื่อ สามารถกำหนดค่าและใช้งานได้ทันทีเมื่อต้องการ ประเภทของฟังก์ชันนี้เหมาะสำหรับการนำมาใช้สร้างพฤติกรรมการปิด (closure)

ปิด (Closures) โดยทั่วไปนิยมประยุกต์ใช้ในฟังก์ชันที่ไม่ระบุชื่อ ซึ่งสามารถจับตัวแปรจากขอบเขตที่ล้อมรอบได้ เมื่อฟังก์ชันไม่ระบุชื่ออ้างถึงตัวแปรจากขอบเขตภายนอก ฟังก์ชันไม่ระบุชื่อพร้อมกับตัวแปรที่ถูกอ้างถึงจัดเป็นปิด (closures)

func main() {
    adder := func(sum int) func(int) int {
        return func(x int) int {
            sum += x
            return sum
        }
    }

    sumFunc := adder()
    println(sumFunc(2))  // ผลลัพธ์: 2
    println(sumFunc(3))  // ผลลัพธ์: 5
    println(sumFunc(4))  // ผลลัพธ์: 9
}

ที่นี่ ฟังก์ชัน adder ส่งคืนฟังก์ชันที่ไม่ระบุชื่อซึ่งจัดเป็นปิด (closures) โดยอ้างถึงตัวแปร sum

2.3 ลักษณะเฉพาะของปิด (Closures)

ลักษณะที่ชัดเจนที่สุดของปิด (closures) คือ ความสามารถในการจำอยู่ในสภาพแวดล้อมที่ถูกสร้างขึ้น สามารถเข้าถึงตัวแปรที่กำหนดขึ้นในฟังก์ชันของตนเอง ลักษณะของปิด (closures) ช่วยในการซ่อนสถานะ (โดยอ้างอิงตัวแปรภายนอก) ซึ่งเป็นฐานหลักในการนำมาประยุกต์ใช้สร้างคุณสมบัติที่มีพลังงานมากมายในการโปรแกรม เช่น การตกแต่ง (decorators) การซ่อนสถานะ และการประเมินค่าโดยการเลี่ยงการดำเนินการที่ใช้เวลา

นอกจากการซ่อนสถานะ การปิด (closures) ยังมีลักษณะดังต่อไปนี้:

  • การยืดอายุของอายุของตัวแปร: อายุของตัวแปรภายนอกที่อ้างอิงโดยปิด (closures) ยืดอายุไปตลอดระยะเวลาชีวิตของปิด (closures)
  • การซ่อนตัวแปรส่วนตัว: วิธีอื่นไม่สามารถเข้าถึงตัวแปรภายในของปิด (closures) โดยตรง นำมาสร้างวิธีในการซ่อนตัวแปรส่วนตัว

2.4 ปัญหาที่พบบ่อยและข้อคิดบางประการ

ในการใช้ปิด (closures) มีปัญหาที่พบบ่อยและรายละเอียดที่ควรพิจารณา:

  • ปัญหาในการผูกตัวแปรวนรอบ: การใช้ตัวแปรการวนรอบโดยตรงเพื่อสร้างปิด (closures) ภายในรอบอาจทำให้เกิดปัญหา เนื่องจากที่อยู่ของตัวแปรการวนรอบไม่เปลี่ยนไปกับการวนรอบแต่ละครั้ง
for i := 0; i < 3; i++ {
    defer func() {
        println(i)
    }()
}
// ผลลัพธ์อาจจะไม่ได้เป็น 0, 1, 2 แต่เป็น 3, 3, 3

เพื่อหลีกเลี่ยงปัญหานี้ ตัวแปรการวนรอบควรถูกส่งเป็นพารามิเตอร์ให้กับปิด (closures):

for i := 0; i < 3; i++ {
    defer func(i int) {
        println(i)
    }(i)
}
// ผลลัพธ์ที่ถูกต้อง: 0, 1, 2
  • การรั่วหน่วยความจำของปิด (closures): หากปิด (closures) มีการอ้างถึงตัวแปรโลคอลที่มีขนาดใหญ่ และปิด (closures) นี้ถูกเก็บไว้นาน เที่ยงตรงจะทำให้ตัวแปรโลคอลไม่ถูกคืน ซึ่งอาจทำให้เกิดการรั่วหน่วยความจำ

  • ปัญหาเกี่ยวกับการประสาความงามของปิด (closures): หากปิด (closures) ถูกประสาที่เวลาที่แตกต่างกันและอ้างถึงตัวแปรบางตัว ปัญหาเกี่ยวกับการประสาความงามจำเป็นต้องตรวจสอบว่าการอ้างถึงนี้ถูกปรอทัก์ด้วยการประสาความงามอย่างที่สุด โดยทั่วไปจะต้องใช้มิวเท็กล็อคเพื่อให้แน่ใจว่าการอ้างถึงนี้ทันสมัยในสภาพการเปลี่ยวแปลง

การเข้าใจปัญหาเหล่านี้และข้อคิดบางประการสามารถช่วยให้นักพัฒนาโปรแกรมใช้ปิด (closures) อย่างปลอดภัยและมีประสิทธิภาพมากยิ่งขึ้น