ถ้าพูดถึงคำว่า Function แล้ว ความคิดแรกทุกคนน่าจะนึกถึงวิชาคณิตศาสตร์ แต่ไหนๆ บล็อกนี้ก็เป็นบล็อก Programming แล้ว เราจะขอพูดถึงเรื่องฟังก์ชันในมุมมองของโปรแกรมเมอร์แทนละกัน
Function คืออะไร
นิยามของฟังก์ชันคือ "Black Box ที่รับค่าบางอย่างเข้าไป แล้วทำการคำนวณแล้วตอบผลลัพธ์กลับมา"
ในวิชาคณิตศาสตร์จะเทียบได้กับกราฟที่ไม่มีจุด x
อยู่ในแนวตั้งเดียวกัน หรือก็คือสำหรับค่า x
ทุกๆ x
จะต้องมี y
คู่กับมันแค่ค่าเดียวเท่านั้น (ถ้าไม่เข้าใจ ดูรูปประกอบข้างล่าง)
ถ้าเทียบกับภาษาโปรแกรม
x
จะเทียบได้กับ inputy
จะเทียบได้กับ output
เหตุผลที่มี output ได้แค่ค่าเดียวก็เพราะฟังก์ชันคือการส่งค่าไปคำนวณหรือประมวลผลอะไรบางอย่างแล้วส่งคำตอบกลับมา แล้วการส่งคำตอบกลับมาเนี่ย มันก็มีได้คำตอบเดียวยังไงล่ะ ... การคำนวณไม่สามารถให้คำตอบ 2 ค่าด้วยคำถามเดียวกันได้นะ (แม้ว่าจะคำนวณผิด ก็ต้องตอบมาค่าเดียวอยู่ดี)
เช่น ถามว่า
1 + 1 = ?
การตอบก็จะต้องตอบว่า 2
(ในกรณีที่ตอบถูก) หรือจะตอบ 3
, 4
, 10
อะไรก็ว่าไป (แน่นอนว่าตอบผิด)
แต่มันไม่สามารถมี output ออกมาสองค่าพร้อมกันได้นะ เช่นบอกว่า 1 + 1 = 2, 3
จะเป็นทั้ง 2 และ 3 ในเวลาเดียวกันเหรอ? แบบนี้ไม่ได้! ... หรือถึงจะออกมาค่าเดียว เช่นเรียก 1 + 1 = 2
แต่ลองเรียกอีกครั้งดันได้ 1 + 1 = 3
แบบนี้ก็ถือว่ามีหลาย output ไม่ได้เหมือนกัน
(คือใส่ค่าเข้าไปเหมือนกัน ต้องได้ค่าคำตอบเดิมออกมา จะเปลี่ยนไปเรื่อยๆ ไม่ได้)
หลักการใช้งานฟังก์ชันคือ
- Declaration: การประกาศฟังก์ชัน
- Call: การเรียกใช้งานฟังก์ชัน
เราจะต้อง declare ฟังก์ชันก่อนเรียกใช้งานเสมอ ไม่งั้นคอมพิวเตอร์ก็จะไม่รู้ว่าฟังก์ชันนี้ต้องทำงานยังไง
f(x) = x + 10
│ │ └─┬──┘
│ │ └─ body
│ └─── parameter(s)
└── function name
สำหรับภาษาโปรแกรมจะต้องเขียนละเอียดขึ้น ต้องมีการกำหนด type ว่า input, output เป็นอะไรด้วย (ฟังก์ชันในคณิตศาสตร์ไม่ต้องกำหนด เพราะใช้ number type เป็นหลักเท่านั้น)
┌─────────────────── function name
│ ┌────────────── parameter(s)
│ │ ┌──── return type
function plusTen(x: Int): Int {
return x + 10
} └─┬──┘
└─ body
หน้าที่หลักของฟังก์ชัน ถ้าพูดในเชิงการเขียนโปรแกรม มันคือการรวมกลุ่ม code ที่ใช้งานบ่อยๆ เข้าไว้ด้วยกันเป็นก้อนเดียว ทำให้เวลาเขียนโปรแกรมขนาดใหญ่ เราไม่จำเป็นต้องเขียนโค้ดซ้ำๆ กันหลายๆ รอบ
หน้าที่ของฟังก์ชันอีกอย่างคือการสร้าง Encapsulation หรือการห่อหุ้ม code กลุ่มหนึ่งแล้วสร้างชื่อเรียก code กลุ่มนั้นแทน เช่นการสร้างฟังก์ชัน sin()
, cos()
, tan()
หรือ print()
ขึ้นมา เวลาเรียกใช้งานก็จะง่ายขึ้น เพราะเราไม่จำเป็นต้องรู้การทำงานภายในของฟังก์ชันเลย รู้แค่ฟังก์ชันจะทำอะไรออกมาให้ก็พอ
ฟังก์ชันทำงานอย่างไร, ในมุมมองของคอมพิวเตอร์
เพื่อให้เข้าใจฟังก์ชันจริงๆ เราต้องรู้ก่อนว่าเวลาฟังก์ชันทำงาน เกิดอะไรขึ้นภายในคอมพิวเตอร์บ้าง
Stack Frame
อย่างที่เรารู้กันว่าตัวแปรทุกตัวที่เราเขียนขึ้นมาในโค้ด ต้องการที่อยู่เพื่อเก็บ value ของมัน (เก็บอยู่ใน RAM ซึ่งเป็น main memory ไง)
แต่ใช่ว่าตัวแปรทั้งหมดจะกองกันอยู่ในพื้นที่เดียวกัน โดยส่วนใหญ่แล้วพื้นที่ในเมโมรี่จะถูกแบ่งออกเป็นส่วนๆ คือ Heap และ Stack โดยในบทความนี้เราจะโฟกัสในส่วนของสแต็ก หรือที่เรียกว่า "Stack Frame" ซึ่งใช้เก็บค่าของตัวแปรแยกกันตาม function ...
function mul(x, y){
var z = x * y
return z
}
main(){
var a = 2
var b = 5
var c = mul(a, b)
}
ตัวอย่างโค้ดข้างบนนี้...
- โค้ดเริ่มต้นทำงานที่ฟังก์ชัน
main
(ในขณะนี้ฟังก์ชันmul
ยังไม่ถูกเรียกให้ทำงาน ดังนั้นตัวแปรทั้งหมดจะยังไม่ถูกจองพื้นที่ในเมโมรี่) --> ฟังก์ชันเริ่มทำงาน จะเกิดการจองพื้นที่ในเมโมรี่ เรียกว่า frame ของmain
- การประมวลผลจะทำงานเรียงตามบรรทัด เริ่มจากการกำหนดค่า
a
,b
ใน 2 บรรทัดแรก --> variable และ value ก็จะถูกจองลงไปในเมโมรี่ (ดูรูปประกอบข้างล่าง) - ต่อมา, ที่บรรทัดที่ 3 ของ
main
มีการ call ฟังก์ชันmul
เกิดขึ้น --> มีการเปิดเฟรมใหม่ของฟังก์ชันmul
ขึ้นมาข้างบนเฟรมของmain
อีกที - โครงสร้างเฟรม
นี่เลยเป็นเหตุผลว่าทำไมเราไม่สามารถเรียกใช้ตัวแปรข้ามฟังก์ชันกันได้ เพราะมันอยู่คนละ stack frame กันไงล่ะ
และการที่มันชื่อว่า Stack นั่นก็แปลว่าการซ้อนกันของเฟรมไม่ได้จำกัดว่าต้องมีแค่ชั้นเดียวเท่านั้น จะมีกี่ชั้นก็ได้ (จนกว่าเมมจะเต็ม)
ลองมาดูอีกตัวอย่างที่ซับซ้อนมากขึ้นกัน
คราวนี้จะเป็นการเรียกฟังก์ชันแบบ 2 ชั้น โดยเราจะเพิ่มฟังก์ชันที่ชื่อว่า pow2
ซึ่งเรียกใช้ฟังก์ชัน mul
ต่ออีกทีหนึ่ง
function mul(x, y){
var z = x * y
return z
}
function pow2(x){
var result = mul(x, x)
return result
}
main(){
var a = 5
var b = pow2(a)
}
การทำงานของโปรแกรมจะเริ่มที่ main
เหมือนเดิมจากนั้น --> pow2
--> mul
การทำงานของเมมโมรี่ก็จะเป็นแบบรูปข้างล่างนี่
ข้อสังเกตคือจำนวนเฟรมจะเพิ่มขึ้นหนึ่งชั้นเมื่อเรียกใช้งาน mul
ในขณะที่เฟรมของฟังก์ชัน pow2
ก็ยังคงค้างอยู่ในเมมโมรี่
นั่นเพราะฟังก์ชัน pow2
นั้นยังทำงานไม่เสร็จและต้องรอค่าจากฟังก์ชัน mul
ซะก่อน
สรุปว่าการเรียกฟังก์ชันซ้อนๆ กัน (Nested) นั้น โปรแกรมจะโปรเซสเฉพาะฟังก์ชันที่ทำงานอยู่ในตอนนั้นเท่านั้น (เป็นเฟรมบนสุดใน Stack Frame) ส่วนเฟรมอื่นๆ จะโดน pause เอาไว้ก่อนจนกว่าเฟรมบนจะทำงานเสร็จ
2 Responses
[…] […]
[…] […]