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) อย่างปลอดภัยและมีประสิทธิภาพมากยิ่งขึ้น