Recursion

กลับมาดูฟังก์ชั่น isPrime(n) กันอีกครั้ง

ดูเหมือนว่า ส่วนประกอบสำคัญสำหรับการทำ isPrime(n) ตัวนึงที่หายไป คือ ตัวดำเนินการที่เรียกว่า “modulo” หรือการหารเอาเศษ ซึ่งเป็นตัวดำเนินการที่สำคัญสำหรับการเช็คคุณสมบัติของการเป็น prime number

และสำหรับบทนี้ หลังจากเรามีเครื่องมือหลายๆชิ้นไม่ว่าจะเป็น ตัวเลข, การบวกลบคูณ, ค่าความจริง และการเปรียบเทียบ ที่ /me พยายามสร้างมันขึ้นมาจากฟังก์ชั่นเล็กๆหลายๆแบบ และเราเครื่องมือสุดท้ายสำหรับการสร้าง modulo ก็คือ “Recursion”

Modulo

กลับมาที่ ฟังก์ชั่น Modulo อีกครั้ง

m%n หรือ m mod n หมายถึง เศษที่เกิดจากการหาร m ด้วย n โดย เศษที่ว่านั้นต้องมีค่าอยู่ในช่วง 0 ถึง n-1 และสามารถหา m%n ได้โดยการลบ m ด้วย n ไปเรื่อยๆ(การลบไปเรื่อยๆมันก็คือการหารนั้นแหละ) จนกว่า m จะมีค่าน้อยกว่า n

สามารถเขียนฟังก์ชั่นนี้ในรูปทั่วๆไปได้ว่า

ผลพวงจาก EP5 ทำให้ /me มีเครื่องมือที่เรียกว่า LESS_THAN_OR_EQUAL(n, m) สำหรับเงื่อนไขใน function MODULO

นั้นแปลว่า เราสามารถสร้าง MODULO(m)(n) ได้ดังนี้

ดูเหมือนว่า เราพร้อมที่จะใช้การ MODULO(m)(n) แล้ว

เอ๋ๆๆๆ ยังไงๆ
ดูเหมือนว่าฟังก์ชั่นที่เราสร้างขึ้นมามันใช้การไม่ได้ แต่แน่นอนว่าสิ่งที่เราทำเป็นสิ่งที่ถูกต้อง แต่ทำไมมันใช้การไม่ได้??

ลองเปิดย้อนกลับไปที่หัวข้อ Evaluation Order ใน EP4 อีกครั้ง

เพราะว่า javascript มีลำดับการคำนวนเป็น Application order หรือเป็นแบบ strict function ทุกๆครั้งที่ call function เจ้า javascript จะทำการ evaluate arguments ทั้งหมดก่อน แล้วจึงค่อย evaluate function’s body

ดังนั้นเมื่อกลับไปที่ MODULO(m)(n) อีกครั้ง จะเห็นว่า เราใส่ MODULO(…)(n) เป็น argument หนึ่งของ IF(b) ทำให้ทุกๆครั้งที่เรียก MODULO(m)(n) เจ้าตัว javascript จะพยายามเรียกตัวเองซ้ำขึ้นมาอีกครั้ง แล้วทำแบบนี้ไปเรื่อยๆจนทำให้ Stack overflow

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

สิ่งที่ได้คือ

เจ้า (x) => {…(x)} ทำให้ IF สามารถ evaluate arguments ได้โดยไม่ทำให้เกิด stack overflow เพราะจังหวะที่มันกำลัง evaluate arguments นั้น มันเจาะลึกลงไปได้เพียงแค่เปลือกนอกของ MODULO(…)(n) เท่านั้นเอง

เย้ มันทำงานได้อย่างถูกต้อง แต่ปัญหาต่อมา กลับมาอยู่ที่ปรัชญาการสร้าง isPrime(n)

เพราะ /me สร้าง MODULO ขึ้นโดยการใช้ MODULO ซ้ำอีกครั้ง ถึงแม้ว่าจะดูไม่แตกต่างจาก const อื่นๆที่เราสร้างกันมาตั้งแต่ต้น แต่ความจริงแล้ว มันแอบมีการอ้างสิ่งที่เรียกว่า ตัวแปร และ assignment semantics เพราะว่าเราไม่สามารถ replace คำว่า “MODULO” ด้วยค่าที่อยู่ฝั่งซ้ายของ const MODULO = … โดยไม่มีตัวแปร MODULO

ซึ่งแน่นอนว่ามันขัดกับแนวคิดที่เราสร้างมาตั้งแต่ต้น

แต่ /me สามารถแก้ไขปัญหานี้ได้ โดยเครื่องมือที่เรียกว่า

Y combinator

Y combinator เป็นชิ้นส่วนสำคัญที่ใช้แก้ปัญหานี้โดยเฉพาะ ทำให้เราสามารถสร้าง a recursive function ได้โดยใช้สิ่งนี้

แค่ดูก็น่าจะรู้ได้ไม่ยากว่า Y combinator ไม่ใช้สิ่งที่อธิบายได้แบบง่ายๆ และ /me จะไม่พยายามอธิบายมัน ถึงแม้ว่าจะเป็น human-readable version ก็ยังอ่านไม่รู้เรื่องเลยทีเดียว (ถ้าว่างจะหาเวลาเขียนอีกที)

แต่น่าเสียดายที่เรากลับไปเจอปัญหาเดียวครั้งก่อนอีกครั้งแต่สามารถแก้ได้โดยใช้ (x)=>{…(x)} และทำให้ Y combinator เปลี่ยนไปเป็น Z combinator (สำหรับ strict languages)

ท้ายที่สุด /me จะใช้ Z combinator กับ MODULO ฉบับดั้งเดิม ได้ดังนี้

ทดสอบดูอีกครั้ง

สุดท้ายก็เอามาประกอบเข้ากับ isPrime(n)

สุดท้ายก่อนที่จะจบตอน /me จำเป็นต้องตามหาเครื่องมืออีกตัวที่หายไป

Divide

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

a/b = 1 + ((a-b)/b) โดยที่ ถ้า a < b แล้ว a/b = 0

สามารถเขียนฟังก์ชั่นนี้ในรูปทั่วๆไปได้ว่า

ทั้งนี้ด้วยความรู้เรื่อง Z combinator ที่สั่งสมมาจะได้ว่า

เย้ๆๆ

จบแล้วสำหรับ recursion ซึ่งเป็นจุดสำคัญที่ทำให้เราสามารถสร้าง modulo และ divide ได้อย่างง่ายดาย(เหรอ?)

สำหรับตอนต่อไป คือจุดเปลี่ยนสำคัญในการสร้าง isPrime(n) เมื่อ /me ต้องผจญกับการสร้าง list และฟังก์ชั่นต่างๆที่จำเป็นสำหรับการใช้ list

PS. อ่านตอนก่อนหน้าได้ที่
EP1: Introduction
EP2: Numbers
EP3: Arithmetic operators
EP4: Booleans
EP5: Predicates & Comparison Operators
EP6: Recursion

ต้นฉบับ Programming with Nothing

แต่สามารถ ดูโค้ดส่วนต่างๆได้ที่ Github จะทยอย push ตามบล๊อคที่ทยอยเขียน

Tell your friend about thisShare on Facebook
Facebook
0Tweet about this on Twitter
Twitter