Function ทำงานยังไง?, ในมุมมองของโปรแกรมเมอร์สาย Imperative
Tags: beginners, functional, programming, howitswork
Published: 2020-02-23 17:06:56

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

## Function คืออะไร

นิยามของฟังก์ชันคือ **"Black Box ที่รับค่าบางอย่างเข้าไป แล้วทำการคำนวณแล้วตอบผลลัพธ์กลับมา"**

![](https://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Function_machine2.svg/1200px-Function_machine2.svg.png)

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

![](https://1.bp.blogspot.com/-oRdNQfZCLPE/Xgopan-ogXI/AAAAAAAASBU/K4V_2NeS4_Empa_NXMlfKh2QLAdP08C2QCNcBGAsYHQ/s1600/fp-1.png)

ถ้าเทียบกับภาษาโปรแกรม 
- `x` จะเทียบได้กับ input
- `y` จะเทียบได้กับ output
  
เหตุผลที่มี output ได้แค่ค่าเดียวก็เพราะฟังก์ชันคือการส่งค่าไปคำนวณหรือประมวลผลอะไรบางอย่างแล้วส่งคำตอบกลับมา แล้วการส่งคำตอบกลับมาเนี่ย มันก็มีได้คำตอบเดียวยังไงล่ะ ... การคำนวณไม่สามารถให้คำตอบ 2 ค่าด้วยคำถามเดียวกันได้นะ (แม้ว่าจะคำนวณผิด ก็ต้องตอบมาค่าเดียวอยู่ดี)

เช่น ถามว่า
```
1 + 1 = ?
```
การตอบก็จะต้องตอบว่า `2` (ในกรณีที่ตอบถูก) หรือจะตอบ `3`, `4`, `10` อะไรก็ว่าไป (แน่นอนว่าตอบผิด) 

แต่มันไม่สามารถมี output ออกมาสองค่าพร้อมกันได้นะ เช่นบอกว่า `1 + 1 = 2, 3` จะเป็นทั้ง 2 และ 3 ในเวลาเดียวกันเหรอ? แบบนี้ไม่ได้! ... หรือถึงจะออกมาค่าเดียว เช่นเรียก `1 + 1 = 2` แต่ลองเรียกอีกครั้งดันได้ `1 + 1 = 3` แบบนี้ก็ถือว่ามีหลาย output ไม่ได้เหมือนกัน 

(คือใส่ค่าเข้าไปเหมือนกัน ต้องได้ค่าคำตอบเดิมออกมา จะเปลี่ยนไปเรื่อยๆ ไม่ได้)

หลักการใช้งานฟังก์ชันคือ
1. Declaration: การประกาศฟังก์ชัน
2. 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* ... 

```js
function mul(x, y){
	var z = x * y
	return z
}

main(){
	var a = 2
	var b = 5
	var c = mul(a, b)
}
```

ตัวอย่างโค้ดข้างบนนี้...
1. โค้ดเริ่มต้นทำงานที่ฟังก์ชัน `main` (ในขณะนี้ฟังก์ชัน `mul` ยังไม่ถูกเรียกให้ทำงาน ดังนั้นตัวแปรทั้งหมดจะยังไม่ถูกจองพื้นที่ในเมโมรี่) --> ฟังก์ชันเริ่มทำงาน จะเกิดการจองพื้นที่ในเมโมรี่ เรียกว่า **frame** ของ `main`
2. การประมวลผลจะทำงานเรียงตามบรรทัด เริ่มจากการกำหนดค่า `a`, `b` ใน 2 บรรทัดแรก --> variable และ value ก็จะถูกจองลงไปในเมโมรี่ (ดูรูปประกอบข้างล่าง)
3. ต่อมา, ที่บรรทัดที่ 3 ของ `main` มีการ call ฟังก์ชัน `mul` เกิดขึ้น --> มีการเปิดเฟรมใหม่ของฟังก์ชัน `mul` ขึ้นมาข้างบนเฟรมของ `main` อีกที
4. โครงสร้างเฟรม

![](https://1.bp.blogspot.com/-WAbRBZZQ7Dw/Xgy586d6dgI/AAAAAAAASB0/L-aj17bDWK02bYo2XkqJsxBGuq2YhvdLQCNcBGAsYHQ/s1600/fp-2.png)


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

และการที่มันชื่อว่า Stack นั่นก็แปลว่าการซ้อนกันของเฟรมไม่ได้จำกัดว่าต้องมีแค่ชั้นเดียวเท่านั้น จะมีกี่ชั้นก็ได้ (จนกว่าเมมจะเต็ม)

ลองมาดูอีกตัวอย่างที่ซับซ้อนมากขึ้นกัน
คราวนี้จะเป็นการเรียกฟังก์ชันแบบ 2 ชั้น โดยเราจะเพิ่มฟังก์ชันที่ชื่อว่า `pow2` ซึ่งเรียกใช้ฟังก์ชัน `mul` ต่ออีกทีหนึ่ง

```js
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` การทำงานของเมมโมรี่ก็จะเป็นแบบรูปข้างล่างนี่
![](https://1.bp.blogspot.com/-wdDKzJtVyMw/Xgy582q2ATI/AAAAAAAASB4/MUxPO_3hayop5JQhBg7MmrqiK2jHWJeeQCNcBGAsYHQ/s1600/fp-3.png)

ข้อสังเกตคือจำนวนเฟรมจะเพิ่มขึ้นหนึ่งชั้นเมื่อเรียกใช้งาน `mul` ในขณะที่เฟรมของฟังก์ชัน `pow2` ก็ยังคงค้างอยู่ในเมมโมรี่
นั่นเพราะฟังก์ชัน `pow2` นั้นยังทำงานไม่เสร็จและต้องรอค่าจากฟังก์ชัน `mul` ซะก่อน

สรุปว่าการเรียกฟังก์ชันซ้อนๆ กัน (Nested) นั้น โปรแกรมจะโปรเซสเฉพาะฟังก์ชันที่ทำงานอยู่ในตอนนั้นเท่านั้น (เป็นเฟรมบนสุดใน Stack Frame) ส่วนเฟรมอื่นๆ จะโดน pause เอาไว้ก่อนจนกว่าเฟรมบนจะทำงานเสร็จ