Categories
BYOB Programming

ฟังก์ชันที่เรียกต้วมันเอง

ฟังก์ชันที่เรียกต้วมันเอง เป็นฟังก์ชันชนิดหนึ่งเรียกว่า Recursive function ทำไมถึงต้องมีฟังก์ชันชนิดนี้ แล้วมันมีประโยชน์อย่างไร มาหาคำตอบกัน

แนวคิดของฟังก์ชันที่เรียกตัวมันเอง มีให้เห็นในปรากฏการณ์ทางธรรมชาติเหมือนกัน เช่น รูปร่างของเกล็ดหิมะ ต้นไม้ ใบเฟิร์น หรือ เปลือกหอย เป็นต้น รูปร่างของพวกมันจะมีหน้าตาเหมือนกัน แต่จะมีขนาดไม่เท่ากัน รูปแบบเหล่านี้เรียกว่า แฟร็กทัล (Fractal)

ในการคำนวณบางอย่าง เช่น การหาค่า factorial 5! = 5 x 4 x 3 x 2 x1 = 120 สามารถเขียนได้อีกรูปแบบหนึ่งคือ 5! = 5 x 4! ซึ่ง 4! = 4 x 3! , 3! = 3 x 2!, 2! = 2 x 1! เราจะเห็นรูปแบบการเรียกตัวมันเองเกิดขึ้นไปเรื่อย ๆ ด้วยตัวเลขที่ลดลงจนกระทั่ง 1! = 1 ซึ่งเป็นรูปแบบที่ไม่สามารถเรียกตัวมันเองได้อีกแล้ว

จากตัวอย่างข้างต้น จะเห็นได้ว่าการสร้างฟังก์ชันเรียกตัวมันเอง ต้องมีเงื่อนไขอยู่ 2 อย่างคือ

  1. ต้องมีรูปแบบพื้นฐานที่ไม่สามารถเรียกตัวมันเองได้ ซึ่งเป็นวิธีหยุดการทำงานของฟังก์ชันเรียกตัวมันเอง มิฉนั้น ฟังก์ชันจะทำงานไปเรื่อย ๆ ไม่มีวันจบ
  2. การเรียกตัวมันเองต้องมีแนวโน้มที่จะไปสู่รูปแบบพื้นฐาน ซึ่งอาจมีมากกว่า 1 แบบก็ได้

การเขียนโปรแกรมด้วยฟังก์ชันแบบนี้ จะทำให้ได้โปรแกรมที่สั้นและเข้าใจได้ง่าย (รึเปล่า) ซึ่งใน BYOB สามารถทำได้เช่นกัน มาลองดูกันคราวหน้านะครับ

Categories
BYOB Programming

หลักการเขียนโปรแกรมใน BYOB ตอนที่ 2

ฟังก์ชัน square สามารถวาดรูปสี่เหลี่ยม 100 x 100 ได้ขนาดเดียวทุกคร้้งที่เรียกใช้ฟังก์ชันนี้ ซึ่งไม่ค่อยยืดหยุ่น ถ้าเราต้องการสี่เหลี่ยมขนาดอื่นละ ทำได้ไหม ตามหลักการแล้ว คือการทำ Generalization ให้กับฟังก์ชัน โดยการส่งผ่านข้อมูลให้กับฟังก์ชัน เพื่อให้ฟังก์ชั่นทำงานได้ยืดหยุ่นขึ้น ไม่เฉพาะเจาะจงอยู่กับการทำงานเพียงแบบเดียว (วาดรูปสี่เหลี่ยม 100 x 100) เช่น ถ้าต้องการวาดสี่เหลี่ยมขนาดอื่นๆ ทำได้ด้วยการให้ข้อมูลขนาด (size) ให้กับฟังก์ชัน square เพื่อให้ได้สี่เหลี่ยมตามขนาดที่ใส่เข้าไปในฟังก์ชัน square (size)

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

โดยสรุป ระดับของการทำ Generalization ให้กับฟังก์ชันต้องดูความเหมาะสม ฟังก์ชันที่ทำงานได้สารพัดอย่างย่อมมีความซับซ้อนตามไปด้วย

การใส่ข้อมูลให้กับฟังก์ชัน ทำให้หน้าตาของมัน (function interface) เปลี่ยนไป ฟังก์ชันด้านซ้ายคือ ฟังก์ชันวาดรูปสี่เหลี่ยมจัตุรัสได้หลายขนาด “square (size) steps” กับฟังก์ชันวาดรูปสี่เหลี่ยมผืนผ้าตามขนาดกว้าง x ยาว  “square (width) steps (height) steps” (ข้อมูลเข้าจะอยู่ในวงเล็บ) 2 ฟังก์ชันนี้มีหน้าตาไม่เหมือนกัน จำนวนข้อมูลเข้าไม่เท่ากัน การทำงานภายในฟังก์ชันก็ไม่เหมือนกัน แต่ยังมีหลักการทำงานคล้ายกันคือ วาดรูปสี่เหลี่ยม

โดยทั่วไป หน้าตาฟังก์ชันทำให้เรารู้คราวๆ ว่ามันสามารถทำอะไรได้บ้าง เริ่มจาก ชื่อของฟังก์ชัน square บ่งบอกว่าเกี่ยวกับสี่เหลี่ยมจัตุรัส เมื่อมีข้อมูล size พอเดาได้ว่าเกี่ยวกับขนาดหรือด้านของสี่เหลี่ยม ส่วน steps ไม่ใช่ข้อมูลเข้า เป็นเพียงข้อมูลเพิ่มเติม (ไม่มีก็ได้) บอกหน่วยของขนาด (ใช้ steps เพราะสี่เหลี่ยมเกิดจากการเดินของตัวละคร Alonzo) ส่วนอีกฟังก์ชันหนึ่ง ใช้ชื่อ square เหมือนกันแต่มีข้อมูลเข้าสองตัว width กับ height บอกขนาดกว้างกับยาวของสี่เหลี่ยมผืนผ้า (จริงๆ ควรใช้ชื่อ rectangle มากกว่า)

ข้อมูลเข้า เช่น size เรียกว่าพารามิเตอร์ (parameter) เมื่ออยู่ในฟังก์ชัน จะเป็นตัวแปรชนิดหนึ่ง ที่รู้จักเพียงภายในของฟังก์ชันนั้นๆ (local variable) ถึงแม้จะมีตัวแปรชื่อเหมือนกันภายนอกฟังก์ชัน ก็จะถือว่าเป็นตัวแปรคนละตัวกัน

เมื่อฟังก์ชันถูกเรียกใช้พร้อมกับข้อมูลเข้า เช่น ต้องการวาดสี่เหลี่ยมจัตุรัสขนาด 50 x 50 ก็ต้องเรียกใช้ฟังก์ชัน “square (50) steps” ตัวเลข 50 เรียกว่าอาร์กิวเมนต์ (argument) เป็นข้อมูลที่จะถูกส่งผ่านฟังก์ชัน เชื่อมเข้ากับตัวแปรพารามิเตอร์ size ภายในฟังก์ชัน square ซึ่งข้อมูลนี้ (50) สามารถนำไปใช้ต่อได้ภายในฟังก์ชัน square

หน้าตาของบล็อกหรือฟังก์ชัน อาจไม่เพียงพอต่อการนำมันไปใช้งาน บางคร้้งต้องอ่านข้อมูลเพิ่มเติม เช่น อ่านข้อมูลความช่วยเหลือ (help) หรืออ่านคู่มือการใช้งานฟังก์ชัน (API: application program interface) ซึ่งจะมีข้อมูลรายละเอียดการใช้งานในแต่ละฟังก์ชัน

Categories
Programming Scratch

หลักการเขียนโปรแกรมใน Scratch ตอนที่ 8

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

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

รูปแบบฟังก์ชันใน Scratch มีหลากหลาย และซับซ้อนกว่าที่คิด มาลองดูตัวอย่างใน Delicious Fish

เมธเธิด (method) คือฟังก์ชันที่ใช้เฉพาะกับตัวละครตัวใดตัวหนึ่ง (object function) คล้ายกับการเขียนโปรแกรมเชิงวัตถุ ปกติเมธเธิดของวัตถุใดจะทำงาน เมื่อมีการส่งสาร (massage) มายังวัตถุนั้นโดยตรง ใน Scratch สามารถสร้างสคริปต์ให้เป็นเมธเธิดได้ โดยสร้างรหัสให้กับวัตถุนั้นโดยเฉพาะ เช่น ปลาทองเมื่อถูกกิน จะส่งรหัส “got-me” ออกไป แต่มีเฉพาะปลาตัวใหญ่เท่านั้นที่รับรหัสนี้ อาจพูดได้ว่า สคริปต์ที่รับรหัส “got-me” ของปลาตัวใหญ่ เป็นเมธเธิดหรือฟังก์ชันของวัตถุ นอกจาก “got-me” แล้ว ยังมีรหัสที่ทำหน้าที่เหมือนกันอีก คือ “got 1” กับ “oh-no”

การส่งและรับรหัสเป็นรูปแบบหนึ่งของฟังก์ชันที่ขึ้นกับเหตุการณ์ การส่งรหัสเป็นเหตุการณ์แบบหนึ่ง (event) และเมื่อมีฟังก์ชันใดที่สนใจรหัสนี้ (event listening) ได้รับรหัสนี้ ก็จะเริ่มทำงานเพื่อตอบสนองกับเหตุการณ์ที่เกิดขึ้น (response function) ตัวอย่างเช่น เมื่อเริ่มต้นเกม ตัวควบคุมเกมจะส่งรหัส “setup” ให้ปลาทุกตัวเตรียมพร้อมสำหรับการเล่น ปลาทุกตัวก็ตอบสนองรหัสนี้ด้วยการซ่อนตัว (hide) เท่านั้นเอง แต่เมื่อเข้าสู่แต่ละระดับเกม ปลาจะได้รับรหัส “level1” ตามมาด้วย “level2” จนกระทั่งจบเกม เมื่อได้รับรหัส “game-over” ปลาแต่ละตัวจะตอบสนองต่างกันไป

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

เมื่อปลาตัวใหญ่กินปลาแต่ละชนิดได้ มันจะรับรหัสจากปลาทั้งสามชนิด และต้องแสดงแอนิเมชั่นการเคี้ยวพร้อมเสียงประกอบ แต่ถ้าต้องเขียนสคริปต์แสดงแอนิเมชั่นที่เหมือนกันหมด สำหรับปลาทั้งสามชนิด จะทำให้สคริปต์ยาวขึ้น และในอนาคตถ้าต้องการเพิ่มปลาชนิดอื่นอีก ก็ต้องเขียนสคริปต์เหมือนกันอีก ซึ่งไม่ใช่วิธีการเขียนโปรแกรมที่ดี ดังนั้นในสคริปต์ปลาตัวใหญ่ จึงนำส่วนที่เป็นแอนิเมชั่น (chomp) ออกมาเป็นฟังก์ชันต่างหาก ซึ่งเป็นฟังก์ชันที่ถูกเรียกใช้ภายในสคริปต์ปลาตัวใหญ่เท่านั้น เป็นฟังก์ชันส่วนตัว (private function) ด้วยวิธีนี้ ฟังก์ชัน oh-on, got 1, got-me จะเรียกใช้ฟังก์ชัน chomp เพื่อแสดงแอนิเมชั่นแทน ซึ่งเป็นการเรียกใช้ฟังก์ชันภายในฟังก์ชันอีกทีหนึ่ง

เมื่อปลาแต่ละชนิดถูกกิน คะแนนจะเปลี่ยนแปลง อาจขึ้นหรือลงก็ได้ และจำนวนปลาแต่ละชนิดที่ถูกกินก็เพิ่มขึ้นด้วย การที่จะตามการเปลี่ยนแปลงนี้ได้ ต้องสร้างตัวแปรโกลบอล (Score, goldfish, goldfish1, greenfish) มาเก็บค่าเหล่านึ้ไว้