อ่าน 22 นาที
ตัวรวมจุดคงที่
ใน ตรรกะเชิงผสม สำหรับ วิทยาศาสตร์คอมพิวเตอร์ ตัว รวมจุดคงที่ (หรือ ตัวรวมจุดคงที่ ) [ 1 ] : หน้า 26 คือ ฟังก์ชันลำดับสูง (กล่าวคือ ฟังก์ชัน ที่รับฟังก์ชันเป็น อาร์กิวเมนต์ )...
ตัวรวมจุดคงที่
ในตรรกะเชิงผสมสำหรับวิทยาศาสตร์คอมพิวเตอร์ตัวรวมจุดคงที่ (หรือตัวรวมจุดคงที่ ) [ 1 ] : หน้า 26 คือฟังก์ชันลำดับสูง (กล่าวคือฟังก์ชันที่รับฟังก์ชันเป็นอาร์กิวเมนต์ ) ที่ส่งคืนจุดคงที่ บางจุด (ค่าที่ถูกแมปไปยังตัวมันเอง) ของฟังก์ชันอาร์กิวเมนต์ หากมีอยู่
ตามหลักการแล้ว ถ้าเป็นตัวรวมจุดตรึง และฟังก์ชันมีจุดตรึงตั้งแต่หนึ่งจุดขึ้นไป แล้วจะเป็นหนึ่งในจุดตรึงเหล่านั้น กล่าวคือ
ตัวรวมจุดคงที่สามารถกำหนดได้ในแคลคูลัสแลมบ์ดาและใน ภาษา การเขียนโปรแกรมเชิงฟังก์ชันและเป็นวิธีการที่ช่วยให้สามารถกำหนดนิยามแบบเรียกซ้ำได้
ตัวรวม Y ในแคลคูลัสแลมบ์ดา
ในแคลคูลัสแลมบ์ดา แบบคลาสสิกที่ไม่มีประเภท ทุกฟังก์ชันจะมีจุดตรึง การใช้งานเฉพาะอย่างหนึ่งคือตัวรวม Y ที่ขัดแย้งกันของHaskell Curry ซึ่งกำหนดโดย [ 2 ] : 131 [หมายเหตุ 1 ] [หมายเหตุ 2 ]
(ในที่นี้ใช้สัญลักษณ์และข้อกำหนดมาตรฐานของแคลคูลัสแลมบ์ดา: Y คือฟังก์ชันที่รับอาร์กิวเมนต์หนึ่งตัวคือfและส่งคืนนิพจน์ทั้งหมดที่อยู่หลังจุดแรก; นิพจน์หมายถึงฟังก์ชันที่รับอาร์กิวเมนต์หนึ่งตัว คือ xซึ่งถือว่าเป็นฟังก์ชัน และส่งคืนนิพจน์โดยที่หมายถึงxที่ใช้กับตัวเอง การวางนิพจน์ติดกันแสดงถึงการประยุกต์ใช้ฟังก์ชันมีการเชื่อมโยงทางซ้าย และมีลำดับความสำคัญสูงกว่าจุด)
การตรวจสอบ
การคำนวณต่อไปนี้ยืนยันว่าจุดนั้นเป็นจุดคงที่ของฟังก์ชันจริง ๆ:
ตามนิยามของ โดยการลดรูป β : แทนที่อาร์กิวเมนต์เชิงรูปแบบfของ Y ด้วยอาร์กิวเมนต์จริงg โดยการลดรูปเบต้า (β-reduction): แทนที่อาร์กิวเมนต์เชิงรูปแบบxของฟังก์ชันแรกด้วยอาร์กิวเมนต์จริง โดยความเท่าเทียมกันครั้งที่สอง ข้างต้น
โดยทั่วไปแล้ว เทอมแลมบ์ดาอาจไม่สามารถลดรูปเบต้าไปเป็นเทอมได้ อย่างไรก็ตาม เทอมทั้งสองสามารถลดรูปเบต้าไปเป็นเทอมเดียวกันได้ ดังที่แสดงไว้
การใช้งาน
เมื่อนำตัวรวม Y ไปใช้กับฟังก์ชันที่มีตัวแปรเดียว มักจะไม่ทำงานจนสิ้นสุด แต่จะได้ผลลัพธ์ที่น่าสนใจกว่าหากนำตัวรวม Y ไปใช้กับฟังก์ชันที่มีตัวแปรสองตัวขึ้นไป ตัวแปรที่เพิ่มเข้ามาอาจใช้เป็นตัวนับหรือดัชนี ฟังก์ชันที่ได้จะมีลักษณะการทำงานคล้ายกับ ลู ป whileหรือforในภาษาโปรแกรมเชิงคำสั่ง
เมื่อใช้งานในลักษณะนี้ ตัวรวม Y จะทำการเรียกซ้ำ อย่างง่าย แคลคูลัสแลมบ์ดาไม่อนุญาตให้ฟังก์ชันปรากฏเป็นเทอมในนิยามของตัวเองเหมือนในภาษาโปรแกรม หลายภาษา แต่สามารถส่งฟังก์ชันเป็นอาร์กิวเมนต์ไปยังฟังก์ชันลำดับสูงกว่าที่นำไปใช้ในลักษณะเรียกซ้ำได้
ตัวรวม Y อาจถูกนำมาใช้ในการสร้างความขัดแย้งของเคอร์รีได้เช่นกัน หัวใจสำคัญของความขัดแย้งของเคอร์รีคือ แคลคูลัสแลมบ์ดาแบบไม่ระบุประเภทนั้นไม่สมเหตุสมผลในฐานะระบบการอนุมาน และตัวรวม Y แสดงให้เห็นถึงสิ่งนี้โดยอนุญาตให้นิพจน์นิรนามแทนค่าศูนย์ หรือแม้แต่หลายค่า ซึ่งไม่สอดคล้องกับตรรกะทางคณิตศาสตร์
ตัวอย่างการใช้งาน
ตัวอย่างการใช้งาน Y ในภาษาRแสดงไว้ด้านล่างนี้:
Y <- \ ( f ) { g <- \ ( x ) f ( x ( x )) g ( g ) }จากนั้นสามารถนำไปใช้ในการคำนวณแฟกทอเรียลได้ดังนี้:
fact <- \ ( f ) \ ( n ) if ( n == 0 ) 1 else n * f ( n - 1 ) Y ( fact )( 5 ) # ให้ผลลัพธ์ 5! = 120ตัวอักษร Y จำเป็นต้องใช้เฉพาะในกรณีที่ไม่มีชื่อฟังก์ชัน การแทนที่คำจำกัดความทั้งหมดลงในบรรทัดเดียวเพื่อให้ไม่จำเป็นต้องระบุชื่อฟังก์ชันจะได้ผลลัพธ์ดังนี้:
( \ ( f ) ( \ ( x ) f ( x ( x )))( \ ( x ) f ( x ( x )))) ( \ ( f ) \ ( n ) ถ้า( n == 0 ) 1 มิฉะนั้นn * f ( n - 1 )) ( 5 )วิธีนี้ได้ผลเพราะ R ใช้การประเมินแบบเลซี่ (lazy evaluation )
ภาษาโปรแกรมที่ใช้การประเมินค่าอย่างเข้มงวดเช่นPython , C++และภาษาโปรแกรมอื่นๆ ที่ใช้การประเมินค่าอย่างเข้มงวดมัก จะสามารถแสดงค่า Y ได้ อย่างไรก็ตาม การนำไปใช้งานใดๆ ก็ไร้ประโยชน์ในทางปฏิบัติ เนื่องจากมันจะวนลูปไปเรื่อยๆ อย่างไม่มีที่สิ้นสุดจนกว่าจะหยุดทำงานเนื่องจากเกิดstack overflow
ตัวรวมจุดคงที่
ตัวรวม Y เป็นการนำตัวรวมจุดคงที่มาใช้ในแคลคูลัสแลมบ์ดา ตัวรวมจุดคงที่สามารถกำหนดได้ง่ายในภาษาฟังก์ชันและภาษาเชิงคำสั่งอื่นๆ การนำมาใช้ในแคลคูลัสแลมบ์ดาทำได้ยากกว่าเนื่องจากข้อจำกัดของแคลคูลัสแลมบ์ดา ตัวรวมจุดคงที่สามารถนำไปใช้ในหลายๆ ด้านได้:
- คณิตศาสตร์ทั่วไป
- แคลคูลัสแลมบ์ดาที่ไม่มีประเภท
- แคลคูลัสแลมบ์ดาแบบพิมพ์
- การเขียนโปรแกรมเชิงฟังก์ชัน
- การเขียนโปรแกรมเชิงคำสั่ง
ตัวรวมจุดคงที่สามารถนำไปใช้กับฟังก์ชันต่างๆ ได้หลากหลาย แต่โดยปกติจะไม่สิ้นสุดการทำงานเว้นแต่จะมีพารามิเตอร์เพิ่มเติม เมื่อฟังก์ชันที่จะกำหนดค่าคงที่อ้างอิงถึงพารามิเตอร์นั้น การเรียกใช้ฟังก์ชันอีกครั้งจะเกิดขึ้น ดังนั้นการคำนวณจึงไม่เริ่มต้นขึ้น แต่จะใช้พารามิเตอร์เพิ่มเติมเพื่อกระตุ้นให้การคำนวณเริ่มต้นขึ้นแทน
ชนิดของจุดตรึงคือชนิดของค่าส่งคืนของฟังก์ชันที่กำลังตรึงอยู่ ซึ่งอาจเป็นจำนวนจริง ฟังก์ชัน หรือชนิดอื่นๆ ก็ได้
ในแคลคูลัสแลมบ์ดาแบบไม่ระบุประเภท ฟังก์ชันที่จะใช้ตัวรวมจุดคงที่สามารถแสดงได้โดยใช้การเข้ารหัส เช่นการเข้ารหัสแบบเชิร์ชในกรณีนี้ เทอมแลมบ์ดาเฉพาะ (ซึ่งกำหนดฟังก์ชัน) จะถูกพิจารณาว่าเป็นค่า การ "รัน" (ลดเบต้า) ตัวรวมจุดคงที่บนการเข้ารหัสจะให้เทอมแลมบ์ดาสำหรับผลลัพธ์ ซึ่งสามารถตีความได้ว่าเป็นค่าจุดคงที่
อีกทางเลือกหนึ่ง ฟังก์ชันอาจถูกพิจารณาว่าเป็นเทอมแลมบ์ดาที่นิยามขึ้นโดยใช้แคลคูลัสแลมบ์ดาเพียงอย่างเดียว
แนวทางที่แตกต่างกันเหล่านี้ส่งผลต่อวิธีที่นักคณิตศาสตร์และโปรแกรมเมอร์อาจมองตัวรวมจุดตรึง (fixed-point combinator) นักคณิตศาสตร์อาจมองว่าตัวรวม Y ที่ใช้กับฟังก์ชันเป็นนิพจน์ที่สอดคล้องกับสมการจุดตรึง และดังนั้นจึงเป็นคำตอบ
ในทางตรงกันข้าม บุคคลที่ต้องการใช้ตัวรวมจุดคงที่ (fixed-point combinator) กับงานเขียนโปรแกรมทั่วไปบางอย่าง อาจมองว่ามันเป็นเพียงวิธีการหนึ่งในการนำการเรียกซ้ำมาใช้เท่านั้น
ค่านิยมและขอบเขต
ฟังก์ชันหลายฟังก์ชันไม่มีจุดตรึงใดๆ ตัวอย่างเช่นฟังก์ชัน f( x) โดยใช้การเข้ารหัสแบบ Church เราสามารถแทนจำนวนธรรมชาติด้วยแคลคูลัสแลมบ์ดาได้ และฟังก์ชันf นี้ สามารถนิยามได้ด้วยแคลคูลัสแลมบ์ดา อย่างไรก็ตามโดเมน ของฟังก์ชันนี้ จะครอบคลุม นิพจน์แลมบ์ดา ทั้งหมดไม่ใช่เฉพาะนิพจน์ที่แทนจำนวนธรรมชาติเท่านั้น ตัวรวม Y ที่ใช้กับfจะให้จุดตรึงสำหรับfแต่จุดตรึงนี้จะไม่แทนจำนวนธรรมชาติหากพยายามคำนวณ Y fในภาษาโปรแกรมจริง จะเกิดลูปอนันต์ขึ้น
หน้าที่เทียบกับการนำไปใช้
ตัวรวมจุดคงที่อาจถูกกำหนดในทางคณิตศาสตร์แล้วนำไปใช้ในภาษาอื่น คณิตศาสตร์ทั่วไปกำหนดฟังก์ชันโดยอาศัยคุณสมบัติเชิงขยาย[ 3 ]กล่าวคือ ฟังก์ชันสองฟังก์ชันจะเท่ากันหากทำการแมปแบบเดียวกัน แคลคูลัสแลมบ์ดาและภาษาโปรแกรมถือว่าเอกลักษณ์ของฟังก์ชันเป็น คุณสมบัติ เชิงเจตนาเอกลักษณ์ของฟังก์ชันขึ้นอยู่กับการนำไปใช้
ฟังก์ชัน (หรือเทอม) ในแคลคูลัสแลมบ์ดา คือการนำฟังก์ชันทางคณิตศาสตร์มาใช้งาน ในแคลคูลัสแลมบ์ดา มีตัวรวม (การใช้งาน) จำนวนหนึ่งที่ตรงตามนิยามทางคณิตศาสตร์ของตัวรวมจุดตรึง
นิยามของคำว่า "คอมไบเนเตอร์"
ตรรกศาสตร์เชิงการจัด เรียง (Combinatory logic)เป็นทฤษฎีฟังก์ชันลำดับสูง ตัว จัดเรียง (Combinator)คือ นิพจน์แลมบ์ดา แบบปิดซึ่งหมายความว่าไม่มีตัวแปรอิสระ ตัวจัดเรียงเหล่านี้สามารถนำมาผสมผสานกันเพื่อกำหนดค่าต่างๆ ไปยังตำแหน่งที่ถูกต้องในนิพจน์โดยไม่ต้องตั้งชื่อตัวแปรเหล่านั้นเลย
นิยามแบบเรียกซ้ำและตัวรวมจุดคงที่
ตัวรวมจุดคงที่สามารถใช้เพื่อกำหนด ฟังก์ชันแบบ เรียกซ้ำได้อย่างไรก็ตาม การใช้งานจริงในการเขียนโปรแกรมนั้นค่อนข้างน้อย[ 4 ]ระบบประเภทแบบนอร์มาไลซ์ที่เข้มงวด เช่นแคลคูลัสแลมบ์ดาแบบกำหนดประเภทอย่างง่ายไม่อนุญาตให้มีการไม่สิ้นสุด ดังนั้นตัวรวมจุดคงที่จึงมักไม่สามารถกำหนดประเภทได้ หรือต้องใช้คุณสมบัติของระบบประเภทที่ซับซ้อน ยิ่งไปกว่านั้น ตัวรวมจุดคงที่มักไม่มีประสิทธิภาพเมื่อเทียบกับกลยุทธ์อื่นๆ ในการนำการเรียกซ้ำมาใช้ เนื่องจากต้องมีการลดฟังก์ชันมากขึ้น และสร้างและแยกทูเปิลสำหรับแต่ละกลุ่มของคำจำกัดความแบบเรียกซ้ำซึ่งกันและกัน[ 1 ] : หน้า 232
ฟังก์ชันแฟกทอเรียล
ฟังก์ชันแฟกทอเรียลเป็นตัวอย่างที่ดีของการใช้ตัวรวมจุดคงที่ในการกำหนดฟังก์ชันเวียนเกิด นิยามเวียนเกิดมาตรฐานของฟังก์ชันแฟกทอเรียลในทางคณิตศาสตร์สามารถเขียนได้ดังนี้
โดยที่n เป็น จำนวนเต็มที่ไม่เป็นลบการนำไปใช้ในแคลคูลัสแลมบ์ดา ซึ่งจำนวนเต็มถูกแทนด้วยการเข้ารหัสแบบเชิร์ชพบปัญหาที่ว่าแคลคูลัสแลมบ์ดาไม่อนุญาตให้ใช้ชื่อฟังก์ชัน ('ข้อเท็จจริง') ในนิยามของฟังก์ชัน ปัญหานี้สามารถแก้ไขได้โดยใช้ตัวรวมจุดคงที่ดังต่อไปนี้
กำหนดฟังก์ชันFที่มีอาร์กิวเมนต์สองตัวคือfและn :
(นี่คือฟังก์ชันที่รับอาร์กิวเมนต์สองตัว และจะส่งคืนอาร์กิวเมนต์ตัวแรกหากn = 0 และส่งคืนค่าอาร์กิวเมนต์ตัวที่สองในกรณีอื่น ๆโดยจะมีค่าเท่ากับn – 1)
ตอนนี้กำหนด. จากนั้นจะเป็นจุดตรึงของFซึ่งทำให้ได้
ตามความต้องการ
ตัวรวมจุดตรึงในแคลคูลัสแลมบ์ดา
ตัวรวม Y ซึ่งค้นพบโดยHaskell Curryถูกนิยามว่า
ตัวรวมจุดคงที่อื่นๆ
ในแคลคูลัสแลมบ์ดาแบบไม่มีประเภท คอมบินาเตอร์จุดตรึงไม่ได้หายากเป็นพิเศษ อันที่จริงมีจำนวนอนันต์[ 5 ]ในปี 2548 Mayer Goldberg แสดงให้เห็นว่าเซตของคอมบินาเตอร์จุดตรึงของแคลคูลัสแลมบ์ดาแบบไม่มีประเภทสามารถ แจงนับ ได้แบบเรียกซ้ำ[ 6 ]
ตัวรวม Y สามารถแสดงในแคลคูลัส SKIได้ดังนี้
ตัวรวมสัญญาณเพิ่มเติม ( ระบบ B, C, K, W ) ช่วยให้การเข้ารหัสสั้นลงมาก ด้วยตัวรวมสัญญาณแบบประยุกต์ใช้เอง เนื่องจากและดังนั้นข้างต้นจึงกลายเป็น
ตัวรวมจุดตรึงที่สั้นที่สุดในแคลคูลัส SK โดยใช้ตัวรวม S และ K เท่านั้น ซึ่งค้นพบโดยJohn Trompคือ
อย่างไรก็ตาม โปรดทราบว่ามันไม่ได้อยู่ในรูปแบบปกติ ซึ่งยาวกว่า ตัวรวมนี้สอดคล้องกับนิพจน์แลมบ์ดา
ตัวรวมจุดตรึงต่อไปนี้เรียบง่ายกว่าตัวรวม Y และลดรูป β ไปเป็นตัวรวม Y บางครั้งจึงถูกอ้างถึงว่าเป็นตัวรวม Y นั่นเอง:
ตัวรวมจุดคงที่ทั่วไปอีกตัวหนึ่งคือตัวรวมจุดคงที่ของทัวริง (ตั้งชื่อตามผู้ค้นพบคืออลัน ทัวริง ): [ 7 ] [ 2 ] : 132
ข้อได้เปรียบเหนือสิ่งอื่นใดคือการลดรูปเบต้าให้เหลือเพียง[หมายเหตุ 3 ] ในขณะที่และลดรูปเบต้าให้เหลือเพียงพจน์ทั่วไปเท่านั้น
นอกจากนี้ยังมี รูปแบบ การส่งค่าแบบ ง่ายๆ อีกด้วย :
สิ่งที่เทียบเคียงได้กับการเรียกซ้ำร่วมกันคือตัวรวมจุดตรึงแบบพหุตัวแปร[ 8 ] [ 9 ] [ 10 ]ซึ่งอาจแสดงด้วย Y*
ตัวรวมจุดคงที่ที่เข้มงวด
ในภาษาการเขียนโปรแกรมที่เข้มงวด ตัวรวม Y จะขยายจนกว่าจะเกิดstack overflowหรือจะไม่หยุดเลยในกรณีของ การเพิ่มประสิทธิภาพ การเรียก tail [ 11 ]ตัวรวม Z จะทำงานในภาษาที่เข้มงวด (เรียกอีกอย่างว่าภาษา eager ซึ่งใช้ลำดับการประเมินแบบ applicative) ตัวรวม Z มีอาร์กิวเมนต์ next ที่กำหนดไว้อย่างชัดเจน ป้องกันการขยายตัวในด้านขวามือของคำจำกัดความ: [ 12 ]
และในแคลคูลัสแลมบ์ดา มันคือการขยายแบบอีตาของตัวรวม Y:
ตัวรวมสัญญาณจุดคงที่ที่ไม่เป็นมาตรฐาน
ถ้า F เป็นตัวรวมจุดตรึงในแคลคูลัสแลมบ์ดาแบบไม่มีประเภทแล้ว จะมี:
เทอมที่มี ต้นไม้โบห์ม (Böhm tree)เดียวกันกับตัวรวมจุดคงที่ (fixed-point combinator) กล่าวคือ มีส่วนขยายอนันต์ (infinite extension ) เดียวกัน เรียกว่า ตัวรวม จุดคงที่ที่ไม่เป็นมาตรฐาน ( non-standard fixed-point combinator) ตัวรวมจุดคงที่ใดๆ ก็เป็นตัวรวมจุดคงที่ที่ไม่เป็นมาตรฐานเช่นกัน แต่ไม่ใช่ว่าตัวรวมจุดคงที่ที่ไม่เป็นมาตรฐานทั้งหมดจะเป็นตัวรวมจุดคงที่ เพราะบางตัวไม่เป็นไปตามสมการจุดคงที่ที่กำหนดตัวรวมจุดคงที่ "มาตรฐาน" ตัวรวมเหล่านี้เรียกว่า ตัวรวมจุดคงที่ที่ไม่เป็นมาตรฐานอย่างเคร่งครัด (strictly non-standard fixed-point combinator ) ตัวอย่างเช่น ตัวรวมต่อไปนี้:
ที่ไหน
เนื่องจาก
การแก้ไขที่สร้างขึ้นแบบเรียลไทม์ซึ่งเพิ่มอินสแตนซ์ลงในห่วงโซ่พร้อมกันในขณะที่ถูกแทนที่ด้วยนั้นอยู่ ที่ไหน
เซตของตัวรวมจุดคงที่ที่ไม่เป็นมาตรฐานไม่สามารถแจงนับได้แบบเรียกซ้ำ[ 6 ]
การนำไปใช้ในภาษาอื่นๆ
ตัวรวม Y เป็นการใช้งานเฉพาะอย่างหนึ่งของตัวรวมจุดคงที่ในแคลคูลัสแลมบ์ดา โครงสร้างของมันถูกกำหนดโดยข้อจำกัดของแคลคูลัสแลมบ์ดา การใช้โครงสร้างนี้ไม่จำเป็นหรือไม่เป็นประโยชน์ในการใช้งานตัวรวมจุดคงที่ในภาษาอื่นๆ
ตัวอย่างง่ายๆ ของตัวรวมจุดคงที่ (fixed-point combinator) ที่นำไปใช้ในรูปแบบการเขียนโปรแกรม บางแบบ มีดังต่อไปนี้
การใช้งานฟังก์ชันแบบขี้เกียจ
ในภาษาที่รองรับการประเมินแบบเลซี่เช่นHaskellเป็นไปได้ที่จะกำหนด combinator จุดคงที่โดยใช้สมการกำหนดของ combinator จุดคงที่ซึ่งโดยทั่วไปเรียกว่าfixเนื่องจาก Haskell มีประเภทข้อมูล แบบเลซี่ combinator นี้จึงสามารถใช้เพื่อกำหนดจุดคงที่ของตัวสร้างข้อมูล (และไม่เพียงแต่ใช้ในการใช้งานฟังก์ชันเรียกซ้ำ) คำจำกัดความมีดังต่อไปนี้ ตามด้วยตัวอย่างการใช้งานบางส่วน ใน Hackage ตัวอย่างดั้งเดิมคือ: [ 13 ]
fix , fix' :: ( a -> a ) -> a fix f = let x = f x in x -- แลมบ์ดาถูกละทิ้ง การแบ่งปัน-- นิยามดั้งเดิมใน Data.Function -- ทางเลือกอื่น: fix' f = f ( fix' f ) -- แลมบ์ดาถูกยกขึ้น ไม่ใช้การแบ่งปันแก้ไข( \ x -> 9 ) -- ค่าที่ได้คือ 9fix ( \ x -> 3 : x ) -- ประเมินค่าเป็นลิสต์อนันต์แบบเลซี่ [3,3,3,...]fact = fix fac -- ประเมินค่าเป็นฟังก์ชันแฟกทอเรียลโดยที่fac f 0 = 1 fac f x = x * f ( x - 1 )ข้อเท็จจริงข้อที่ 5 -- มีค่าเท่ากับ 120การใช้งานฟังก์ชันอย่างเคร่งครัด
ในภาษาโปรแกรมเชิงฟังก์ชันอย่างเคร่งครัด ดังตัวอย่างด้านล่างที่ใช้OCamlนั้น อาร์กิวเมนต์ของฟังก์ชันfจะถูกขยายล่วงหน้า ทำให้เกิดลำดับการเรียกที่ไม่สิ้นสุด
- .
ปัญหานี้อาจแก้ไขได้โดยการกำหนดค่า fix ด้วยพารามิเตอร์เพิ่มเติม
ให้rec fix f x = f ( fix f ) x (* โปรดสังเกต x ที่เกินมา ดังนั้น fix f = \x-> f (fix f) x *)ให้factabs fact = ฟังก์ชัน(* factabs มีระดับนามธรรมแลมบ์ดาเพิ่มเติม *) 0 -> 1 | x -> x * fact ( x - 1 )let _ = ( fix factabs ) 5 (* ประเมินค่าเป็น "120" *)ในภาษาฟังก์ชันหลายกระบวนทัศน์ (ภาษาที่ตกแต่งด้วยคุณสมบัติเชิงคำสั่ง) เช่นLispปีเตอร์ แลนดินแนะนำให้ใช้การกำหนดค่าตัวแปรเพื่อสร้างตัวรวมจุดคงที่[ 14 ]ดังตัวอย่างด้านล่างโดยใช้Scheme :
( define Y! ( lambda ( f ) (( lambda ( g ) ( set! g ( f ( lambda ( x ) ( g x )))) ;; (set! g expr) กำหนดค่า expr, g ให้กับg ) ;; แทนที่ค่าเริ่มต้นของ g คือ #f สร้าง#f ))) ;; ดังนั้นค่าที่อ้างอิงตัวเองอย่างแท้จริงใน gการใช้แคลคูลัสแลมบ์ดาที่มีสัจพจน์สำหรับคำสั่งการกำหนดค่า สามารถแสดงได้ว่าY!สอดคล้องกับกฎจุดคงที่เดียวกันกับตัวรวม Y แบบเรียกโดยค่า: [ 15 ] [ 16 ]
ในการใช้งาน Scheme สมัยใหม่ที่เป็นไปตามแบบแผนมากขึ้น โดยทั่วไปแล้วจะจัดการเรื่องนี้ผ่านทางletrecนิพจน์ เนื่องจากขอบเขตทางคำศัพท์ (lexical scope)ถูกนำมาใช้ใน Lisp ในช่วงทศวรรษ 1970:
( define Y* ( lambda ( f ) ( letrec ;; (letrec ((g expr)) ...) locally defines g (( g ;; as expr recursively: g in expr refers to ( f ( lambda ( x ) ( g x ))))) ;; that same g being defined, g = f (λx. gx) g ))) ;; ((Y* f) ...) = (g ...) = ((f (λx. gx)) ...)หรือไม่มีฉลากภายใน:
( define Y* ( lambda ( f ) (( lambda ( i ) ( i i )) ( lambda ( i ) ( f ( lambda x ( apply ( i i ) x )))))))การนำภาษาเชิงบังคับมาใช้
ตัวอย่างนี้เป็นการนำตัวรวมจุดคงที่ (fixed-point combinator) มาใช้ในรูปแบบที่ตีความได้เล็กน้อย มีการใช้คลาสเพื่อบรรจุfix()ฟังก์ชันที่เรียกว่าFixedPointCombinator`fixer` ฟังก์ชันที่จะถูกตรึง (fixed function) นั้นอยู่ในคลาสที่สืบทอดมาจาก `fixer` fix()ฟังก์ชัน `fixer` เข้าถึงฟังก์ชันที่จะถูกตรึงโดยใช้แนวคิดในการเรียกapply()`fixer` ส่วนในแง่ของนิยามฟังก์ชันที่เข้มงวดนั้น ` fixer` fix()จะได้รับพารามิเตอร์พิเศษ `define` อย่างชัดเจนxซึ่งหมายความว่าไม่จำเป็นต้องมีการประเมินแบบเลซี่ (lazy evaluation)
โดยใช้std :: same_as ;template < typename Ret , typename Arg , typename T > concept FixedPointApplicable = requires ( Arg x ) { { T :: apply ( x ) } -> same_as < Ret > ; };template < typename Ret , typename Arg , FixedPointApplicable < Ret , Arg > Derived > class FixedPointCombinator { public : static Ret fix ( Arg x ) noexcept { return Derived :: apply ( x ); } };class Factorial : public FixedPointCombinator < long , long , Factorial > { static long apply ( long x ) noexcept { if ( x == 0 ) { return 1 ; } return x * fix ( x - 1 ); } };ผลลัพธ์ยาว= Factorial :: fix ( 5 );โดยใช้เพียงฟังก์ชันแลมบ์ดา เราสามารถสร้างตัวรวมจุดคงที่ได้ดังนี้:
auto fix = []( auto f ) { return [ f ]( auto && ... args ) -> decltype ( auto ) { return f ( f , std :: forward < decltype ( args ) > ( args )...); }; };auto factorial = fix ([]( auto self , long n ) -> long { return n == 0 ? 1 : n * self ( self , n - 1 ); });std :: println ( "5! = {}" , factorial ( 5 )); // พิมพ์ 120สามารถแสดงตัวอย่างเพิ่มเติมเพื่อสาธิตการใช้แคลคูลัสตัวรวม SKI (โดยใช้ชื่อนกที่กำหนดจากตรรกะเชิงผสม ) ในการสร้างตัวรวม Z เพื่อให้ได้พฤติกรรมคล้าย การร้องหาง ผ่านการกระโดดบนแทรมโพลีนได้:
// Combinators const K = < A , B > ( a : A ) => ( _b : B ) => a ; // Kestrel const S = < A , B , C > ( a : ( x : C ) => ( y : B ) => A ) => ( b : ( x : C ) => B ) => ( c : C ) => a ( c )( b ( c )); // Starling// ตัวรวมที่ได้มาconst I = S ( K )( K ); // เอกลักษณ์const B = S ( K ( S ))( K ); // นกบลูเบิร์ดconst C = S ( B ( B )( S ) )( K ( K )); // นกคาร์ดินัลconst W = C ( S )( I ); // นกวอร์เบลอร์const T = C ( I ); // นกทรัชconst V = B ( C )( T ); // นกไวรีโอconst I1 = C ( C ( I )); // เอกลักษณ์นกที่ลบออกหนึ่งขั้น; เหมือนกับ C(B(B)(I))(I) const C1 = B ( C ); // นกคาร์ดินัลที่ลบออกหนึ่งขั้นconst R1 = C1 ( C1 ); // นกโรบินที่ลบออกหนึ่งขั้นconst V1 = B ( R1 )( C1 ); // นกไวรีโอที่ลบออกหนึ่งขั้นconst I2 = R1 ( V ); // เอกลักษณ์นกที่ลบออกสองขั้น// ตัวรวม Z const Z = B ( W ( I1 ))( V1 ( B )( W ( I2 )));const Z2 = S ( K ( S ( S ( K ( S ( S ( K )( K ))( S ( K )( K ))))( S ( K ( S ( K ( S ))( K )))( S ( K ( S ( S ( K )( K ))))( K ))))( K ( S ( S ( K ) ))))))( S ( S ( K ( S ( S ( K ( S ( K ( S ))( K )) )( S ) )( K ( K ))))( S ( K ( S ( S ( K ( S ( K ( S )) ( K )))( S )) ( K ( K ))))( S ( K ( S ))( K ))))( K ( S ( S ( K ( S ( S ( K )( K ))( S ( K )( K ))))( S ( K ( S ( K ( S )) ( K ) ) )( S ( K ( S ( S ( K )( K ) )))( K ))))( K ( S ( K ( S ( S ( K ( S ( S ( K ( S ( K ( S ) ) ( K )))( S ))( K ( K ))))( S ( K ( S ( S (K ( S ( K ( S )) ( K ) ))( S ))( K ( K ))))( S ( K ( S ( S ( K )( K ))))( K ))))))( K )))))); // รูปแบบขยายเต็มรูปแบบทางเลือกconst Z3 = S ( S ( K ( S ( S )( K ( S ( S ( K ) ( K ))( S ( K )( K ))))))( K ))( S ( S ( K ( S ))( K ))( K ( S ( S ( K ( S ))( S ( K ( S ( K ( S ( K ( S ) ( K ( K ) ) )) ( K )))( S )))( S ( S ( K )( K ) ))))( K ) ))( K ( K ( S ( S ( K ) ( K ))( S ( K )( K )))))))); // อีกเวอร์ชันที่สั้นกว่าconst trampoline = < T > ( fn : T | (() => T )) : T => { let ctx = fn ; while ( typeof ctx === "function" ) { ctx = ( ctx as () => T )(); } return ctx ; };const countFn = ( self : ( n : number ) => any ) => ( n : number ) : any => n === 0 ? ( console . log ( n ), n ) : () => self ( n - 1 ); // คืนค่า thunk "() => self(n - 1)" แทน// ตัวอย่างtrampoline ( Z ( countFn )( 10 )); trampoline ( Z2 ( countFn )( 10 )); trampoline ( Z3 ( countFn )( 10 ));กำลังพิมพ์
ในระบบ F ( แคลคูลัสแลมบ์ดา แบบโพลีมอร์ฟิก) ตัวรวมจุดคงที่แบบโพลีมอร์ฟิกมีประเภท[ 17 ]
- ∀a.(a → a) → a
โดยที่เป็นตัวแปรประเภทนั่นคือ ถ้าประเภทของ ที่สอดคล้องกับสมการคือ—ประเภททั่วไปที่สุด—แล้วประเภทของ ก็คือดังนั้น จึงรับฟังก์ชันที่แมปไปยังและใช้ฟังก์ชันนั้นเพื่อส่งคืนค่าประเภท
ในแคลคูลัสแลมบ์ดาแบบง่ายที่ขยายด้วยชนิดข้อมูลแบบเรียกซ้ำสามารถเขียนตัวดำเนินการจุดคงที่ได้ แต่ชนิดของตัวดำเนินการจุดคงที่ที่มีประโยชน์ (ตัวที่เมื่อนำไปใช้แล้วจะส่งคืนค่าเสมอ) อาจถูกจำกัด
ในแคลคูลัสแลมบ์ดาแบบพิมพ์อย่างง่าย ตัวรวมจุดคงที่ Y ไม่สามารถกำหนดประเภทได้[ 18 ]เพราะในบางจุดมันจะจัดการกับเทอมย่อยการประยุกต์ใช้ตัวเองโดยกฎการประยุกต์ใช้:
โดยที่ชนิดข้อมูลอนันต์นั้นมีอยู่ไม่มีตัวรวมจุดคงที่ใดที่สามารถกำหนดชนิดข้อมูลได้ ในระบบเหล่านั้น การสนับสนุนการเรียกซ้ำใดๆ จะต้องถูกเพิ่มเข้าไปในภาษาอย่างชัดเจน
พิมพ์สำหรับตัวรวม Y
ในภาษาโปรแกรมที่รองรับชนิดข้อมูลแบบเรียกซ้ำที่มีชื่อ การเรียกซ้ำแบบไม่จำกัดในซึ่งสร้างชนิดข้อมูลที่ไม่มีที่สิ้นสุดจะถูกทำลายโดยการระบุชื่อชนิดข้อมูลอย่างชัดเจน เช่น ชนิดข้อมูลที่ถูกกำหนดให้เป็นไอโซมอร์ฟิกกับ (หรือเป็นเพียงคำพ้องความหมายของ) ชนิดข้อมูลดังนั้น จึงได้รับการยอมรับว่าเป็นชนิดข้อมูลแบบเรียกซ้ำ ข้อมูล (ค่า) ของชนิดข้อมูลจะถูกสร้างขึ้นโดยการติดแท็กให้กับค่าที่เป็นฟังก์ชันที่มีชนิดข้อมูลโดยใช้แท็กตัวสร้างข้อมูลสำหรับชนิดข้อมูลนั้น
ตัวอย่างเช่น ในโค้ด Haskell ต่อไปนี้ ให้Recเป็นชื่อแท็กนั้น และดังนั้นRecและappเป็นชื่อของทิศทางทั้งสองของไอโซมอร์ฟิซึม โดยมีประเภทดังนี้: [ 19 ] [ 20 ]
Rec :: ( R a -> a ) -> R a app :: R a -> ( R a -> a )(โดยที่"มีประเภท") ซึ่งทำให้เราสามารถเขียนได้ดังนี้:
newtype R a = Rec { app :: R a -> a } -- app (Rec g) = g -- g :: R a -> a -- Rec g :: R a -- app :: R a -> (R a -> a)y :: ( a -> a ) -> a y f = ( \ x -> f ( app x x )) ( Rec ( \ x -> f ( app x x ))) -- x :: R a -- app x :: R a -> a -- app xx :: aนิยามของแคลคูลัสแลมบ์ดาตามปกติ(โดยที่และดังนั้นเทอมจึงเป็นพยานถึงความเท่าเทียมกัน) มีแก่นหลักคือการประยุกต์ใช้ตัวเองซึ่งไม่สามารถกำหนดประเภทได้ในภาษาโปรแกรมแบบกำหนดประเภทคงที่ แต่การแสดงออกข้างต้นสามารถกำหนดประเภทได้ หรือกล่าวอีกนัยหนึ่ง เราสามารถกำหนดนิยามใหม่ของตัวรวมการประยุกต์ใช้ตัวเองและใช้มันได้ดังนี้ app x x
U' g = g ( Rec g ) y f = U' ( \ Rec g -> f ( U' g )) -- g (Rec g) = f (g (Rec g))U'' x = app x x y f = U'' ( Rec ( \ x -> f ( U'' x ))) -- app xx = f (app xx)หรือในภาษา OCaml:
พิมพ์' a recc = In ของ( ' a recc -> ' a ) ปล่อยออก( In x ) = xให้y f = ( fun x a -> f ( out x x ) a ) ( In ( fun x a -> f ( out x x ) a ))หรืออีกทางเลือกหนึ่ง:
ให้y f = ( fun x -> f ( fun z -> out x x z )) ( In ( fun x -> f ( fun z -> out x x z )))บางภาษาอนุญาตให้ระบุประเภททั่วไปว่าเป็นแบบเรียกซ้ำได้โดยชัดเจน แม้ว่าจะไม่ได้ตั้งชื่อประเภทเหล่านั้นก็ตาม การทำเช่นนั้นยังช่วยให้สามารถกำหนดตัวรวม (combinator) ในภาษาเหล่านั้น ได้ด้วย
ข้อมูลทั่วไป
เนื่องจากตัวรวมจุดคงที่สามารถใช้ในการสร้างการเรียกซ้ำได้ จึงสามารถใช้ตัวรวมจุดคงที่เพื่ออธิบายการคำนวณแบบเรียกซ้ำเฉพาะประเภทได้ เช่นการวนซ้ำจุดคงที่วิธีการวนซ้ำการเชื่อมต่อแบบเรียกซ้ำในฐานข้อมูลเชิงสัมพันธ์การวิเคราะห์การไหลของข้อมูลชุด FIRST และ FOLLOW ของสัญลักษณ์ที่ไม่ใช่เทอร์มินัลในไวยากรณ์แบบไร้บริบทการปิดแบบถ่ายทอดและการดำเนินการ ปิด ประเภทอื่นๆ
ฟังก์ชันที่ทุกค่าอินพุตเป็นจุดคงที่ เรียกว่าฟังก์ชันเอกลักษณ์ เขียนอย่างเป็นทางการได้ดังนี้:
ตรงกันข้ามกับการหาปริมาณแบบสากลเหนือทุกสิ่งตัวรวมจุดตรึงจะสร้าง ค่า หนึ่งค่าที่เป็นจุดตรึงของ คุณสมบัติที่โดดเด่นของตัวรวมจุดตรึงคือมันสร้างจุดตรึงสำหรับฟังก์ชันที่กำหนดให้โดยพลการ
ฟังก์ชันอื่นๆ มีคุณสมบัติพิเศษคือ เมื่อใช้งานไปแล้วครั้งหนึ่ง การใช้งานในครั้งต่อไปจะไม่มีผลใดๆ กล่าวอย่างเป็นทางการคือ:
ฟังก์ชันดังกล่าวเรียกว่าฟังก์ชันเอกลักษณ์ (ดูเพิ่มเติมที่การฉายภาพ (คณิตศาสตร์) ) ตัวอย่างของฟังก์ชันดังกล่าวคือฟังก์ชันที่ส่งคืนค่า 0 สำหรับจำนวนเต็มคู่ทั้งหมด และค่า 1 สำหรับจำนวนเต็มคี่ทั้งหมด
ในแคลคูลัสแลมบ์ดาจากมุมมองทางด้านการคำนวณ การใช้ตัวรวมจุดตรึงกับฟังก์ชันเอกลักษณ์หรือฟังก์ชันที่ยกกำลังสองได้เอง มักจะส่งผลให้การคำนวณไม่สิ้นสุด ตัวอย่างเช่น การหาค่า
โดยที่พจน์ที่ได้จะลดรูปเหลือเพียงตัวมันเองและแสดงถึงวงวนไม่สิ้นสุด
ตัวรวมจุดตรึงไม่จำเป็นต้องมีอยู่ในแบบจำลองการคำนวณที่เข้มงวดกว่า ตัวอย่างเช่น มันไม่มีอยู่ในแคลคูลัสแลมบ์ดาแบบกำหนดประเภทอย่างง่าย
ตัวรวม Y อนุญาตให้กำหนดการเรียกซ้ำเป็นชุดของกฎการเขียนใหม่ [ 21 ] โดยไม่จำเป็นต้อง มีการสนับสนุนการเรียกซ้ำแบบดั้งเดิมในภาษา[ 22 ]
ในภาษาโปรแกรมที่รองรับฟังก์ชันนิรนามตัวรวมจุดคงที่ช่วยให้สามารถกำหนดและใช้ ฟังก์ชัน เรียกซ้ำ นิรนาม ได้ กล่าวคือ โดยไม่ต้องผูกฟังก์ชันดังกล่าวเข้ากับตัวระบุในบริบทนี้ การใช้ตัวรวมจุดคงที่บางครั้งเรียกว่าการเรียกซ้ำนิรนาม[หมายเหตุ 4 ] [ 23 ]
ดูเพิ่มเติม
- ฟังก์ชันนิรนาม
- การวนซ้ำจุดคงที่
- แคลคูลัสแลมบ์ดา #การเรียกซ้ำและจุดคงที่
- การยกแลมบ์ดา
- ทฤษฎีบทจุดตรึงของลอว์เวียร์
- ให้การแสดงออก
หมายเหตุ
- ^ ตลอดทั้งบทความนี้ จะใช้กฎไวยากรณ์ที่ระบุไว้ในนิยามแคลคูลัสแลมบ์ดา § สัญกรณ์ เพื่อประหยัดวงเล็บ
- ^ตามที่ Barendregt ระบุไว้ในหน้า 132 ชื่อนี้มีที่มาจาก Curry
- ^
- ^คำศัพท์นี้ดูเหมือนจะเป็น [[ความรู้พื้นฐานทางคณิตศาสตร์|]] เป็นส่วนใหญ่ แต่ก็ปรากฏอยู่ใน:
- Trey Nash, Accelerated C# 2008 , Apress, 2007, ISBN 1-59059-873-3หน้า 462-463 ดัดแปลงมาจากบล็อกของWes Dyer เป็นส่วนใหญ่ (ดูหัวข้อถัดไป)
- บทความเรื่อง "Wes Dyer Anonymous Recursion in C#"ลงวันที่ 2 กุมภาพันธ์ 2550 มีตัวอย่างที่คล้ายคลึงกันกับในหนังสือเล่มข้างต้น แต่มีการอธิบายเพิ่มเติมด้วย
ลิงก์ภายนอก
- ทฤษฎีการเรียกซ้ำและความสุขโดย แมนเฟรด ฟอน ธุน (ปี 2002 หรือก่อนหน้านั้น)
- แคลคูลัสแลมบ์ดา - บันทึกโดย ดอน บลาเฮตา, 12 ตุลาคม 2000
- Y Combinator ถูกเก็บถาวรเมื่อวันที่ 23 มีนาคม 2009 ในWayback Machine
- "การประยุกต์ใช้ Y Combinator ในภาษา Ruby"
- "การเขียนโปรแกรมเชิงฟังก์ชันในภาษา Ada"
- รหัสโรเซตตา - ตัวรวม Y
สรุปเนื้อหา
ข้อมูลสำคัญจากบทความ
ข้อมูลสำคัญเกี่ยวกับ ตัวรวมจุดคงที่
ใน ตรรกะเชิงผสม สำหรับ วิทยาศาสตร์คอมพิวเตอร์ ตัว รวมจุดคงที่ (หรือ ตัวรวมจุดคงที่ ) [ 1 ] : หน้า 26 คือ ฟังก์ชันลำดับสูง (กล่าวคือ ฟังก์ชัน ที่รับฟังก์ชันเป็น อาร์กิวเมนต์ )...
ตัวรวม Y ในแคลคูลัสแลมบ์ดา
ใน แคลคูลัสแลมบ์ดา แบบคลาสสิกที่ไม่มีประเภท ทุกฟังก์ชันจะมีจุดตรึง การใช้งานเฉพาะอย่างหนึ่งคือตัวรวม Y ที่ขัดแย้งกันของ Haskell Curry ซึ่งกำหนดโดย [ 2 ] : 131 [ หมายเหตุ 1 ] [ หมายเหตุ 2 ] เอฟ ฉัน x {\displaystyle \mathrm {fix} }
การตรวจสอบ
การคำนวณต่อไปนี้ยืนยันว่าจุดนั้นเป็นจุดคงที่ของฟังก์ชันจริง ๆ: วาย จี {\displaystyle \mathrm {Y} g} จี {\displaystyle g}
การใช้งาน
เมื่อนำตัวรวม Y ไปใช้กับฟังก์ชันที่มีตัวแปรเดียว มักจะไม่ทำงานจนสิ้นสุด แต่จะได้ผลลัพธ์ที่น่าสนใจกว่าหากนำตัวรวม Y ไปใช้กับฟังก์ชันที่มีตัวแปรสองตัวขึ้นไป ตัวแปรที่เพิ่มเข้ามาอาจใช้เป็นตัวนับหรือดัชนี ฟังก์ชันที่ได้จะมีลักษณะการทำงานคล้ายกับ ลู ป while หรือ...