วงแหวนเว็บ

neizod's speculation

insufficient data for meaningful answer

การเรียกตัวเองบนฟังก์ชันแบบแลมบ์ดา

พอถล่ำลึกลงไปในการเขียนโปรแกรมเชิงฟังก์ชันซักพักหนึ่ง จะเริ่มเกิดคำถามขึ้นมาว่า

“แล้วเราจะเรียกตัวเองบนแลมบ์ดาได้หรือเปล่า?”

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

(lambda x: x**2)(5)

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

(lambda w, x: x**2)(999, 5)

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

(lambda w, x: x**2)(lambda u: 42, 5)

นั่นหมายความว่าแลมบ์ดาไม่จำเป็นต้องไร้ชื่อเสียทีเดียว แน่หล่ะว่ามันอาจจะไร้ชื่อในขอบเขตตัวแปรหนึ่งๆ แต่กับขอบเขตที่ใหญ่ขึ้นกว่าตัวมันเองแล้ว เราสามารถตั้งชื่อให้มันได้! ถึงตอนนี้จะเปลี่ยนชื่อตัวแปรกันซักหน่อย พร้อมกับเอาฟังก์ชันสำหรับคำนวณจากด้านบนมาใส่แทน lambda u: 42 ไปซะ

(lambda g, y: y**2)(lambda x: x**2, 5)

จะเห็นว่าเลข 5 นั้นถูกเก็บไว้ในตัวแปร y แล้วเราคำนวณ y**2 ตรงๆ จากฟังก์ชันชั้นนอกตรงๆ โดยที่ lambda x: x**2 ไม่ได้แสดงบทบาทอะไร แต่เนื่องจากฟังก์ชัน lambda x: x**2 ถูกขอบเขตด้านหน้าแปะป้ายชื่อให้ว่า g เป็นที่เรียบร้อยแล้ว ดังนั้น เราสามารถส่งผ่านเลข 5 ให้ไปทำงานกับ lambda x: x**2 ได้ดังนี้

(lambda g, y: g(y))(lambda x: x**2, 5)

แม้ว่าตอนนี้เราจะสามารถเรียกชื่อของ lambda x: x**2 ในขอบเขตที่ใหญ่กว่าตัวมันได้ แต่มันก็ไม่มีประโยชน์เลยเพราะการจะเรียกตัวเองนั้น เราต้องสามารถเรียกชื่อตัวมันเองในขอบเขตที่มันอาศัยอยู่ได้ ดังนั้น เราจะใช้ท่าเดิมคือส่งผ่านเจ้า lambda x: x**2 นี้ต่อไปเรื่อยๆ ไม่ให้มันหายไป ซึ่งก็ทำได้โดย

(lambda g, y: g(g, y))(lambda f, x: x**2, 5)

เท่านี้ ในขอบเขตของ lambda f, x: x**2 มันก็สามารถเรียกตัวเองซ้ำได้แล้ว (ชื่อว่า f) ถึงตอนนี้ก็ได้เวลาลองเขียนการเรียกตัวเองดูซักหน่อย เราอาจเริ่มจากฟังก์ชันง่ายๆ อย่างแฟคทอเรียลก็ได้

(lambda g, y: g(g, y))(lambda f, x: 1 if x == 0 else x * f(f, x-1), 5)

สังเกตว่าการเรียกตัวเองผ่านการเขียนแลมบ์ดาเช่นนี้ เวลานิยามฟังก์ชันเรียกตัวเอง เราจะต้องส่งชื่อฟังก์ชันตัวเองเป็นหนึ่งในตัวแปรเสมอนะ

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

neizod

author