FP(01) – Pure, First-Class, High-Order: พื้นฐานแห่ง Functional Programming
Tags: beginners, programming, functional
Published: 2020-03-18 04:43:22

> บทความจาก [https://www.tamemo.com/post/157/fp-01-fundamental/](https://www.tamemo.com/post/157/fp-01-fundamental/)

![](https://2.bp.blogspot.com/-fw3eKjLT3BU/XkEw592HOTI/AAAAAAAASIY/HHC8d2LzAnMJGl8IS6nEmguF5nKzVIUdgCNcBGAsYHQ/s1600/fp-4.png)

ในโลกของ FP นั้นมีหลายสิ่งที่ต้องเรียนรู้กัน แต่เราจะมาเริ่มกับสิ่งที่เป็นพื้นฐานที่สุดกันก่อน นั่นคือ 
1. Pure Function
2. First-Class Function
3. High-Order Function

## Pure Function

ความจริงแล้วคุณสมบัติที่สำคัญที่สุดใน FP คือ **First-Class Function** แต่เรื่องของ Pure Function นั้นเข้าใจง่ายกว่าเลยขอยกมาเล่าก่อนละกันนะ (แต่หากใครยังไม่แม่นเรื่องฟังก์ชันคืออะไรให้ไป[อ่านที่นี่](https://www.tamemo.com/post/152/how-function-work/)ก่อนนะ)

สำหรับวิชาคณิตศาสตร์แล้ว ฟังก์ชันคือสิ่งที่ **รับค่าจำนวนหนึ่งเข้าไป (input) แล้วทำการคำนวณให้ได้ผลลัพธ์ 1 ค่าแล้วตอบกลับมา (output)**

เช่น

```
f(x) = x + 10
```
นั่นหมายความว่าเราสร้างฟังก์ชัน `f` ขึ้นมาซึ่งจะรับค่าเข้าไปแล้วเอาไป `+10` เวลาจะเรียกใช้งานก็เช่น `f(20)` ก็จะได้ค่าเท่ากับ `20+10` = `30` ...ตรงไปตรงมามาก ซึ่งถ้าเราจะเขียนแบบภาษาโปรแกรม เราก็จะได้โค้ดแบบนี้
```js
function plusTen(x){
	return x + 10
}
```
แต่การเขียนฟังก์ชันในการเขียนโปรแกรมนั้น เราสามารถสร้างฟังก์ชันที่ไม่รับ input (หรือก็คือ parameter นั่นแหละ) อะไรเลย มีแต่ return ค่ากลับมาเลย หรือแม้แต่สร้างฟังก์ชันที่ไม่ตอบค่าอะไรกลับมาเลยก็ยังไงนะ
```js
// ตอบ 10 ทุกรอบที่มีการเรียกใช้งาน ไม่ค่อยมีประโยชน์เท่าไหร่
function justTen(){
	return 10
}

// รับค่าเข้าไปคำนวณเล่นๆ ไม่ตอบอะไรกลับมาเลย ดูไร้ประโยชน์มาก
function justReceive(x){
  x = x + 10
}
```
> **📢 Note:** ดังนั้นการสร้าง Pure Function ที่ดี ต้องรับ input เข้าไปอย่างน้อย 1 ตัว แล้ว return ค่ากลับ 1 ค่าเสมอ
> ฟังก์ชันนั้นถึงจะเอามาใช้งานได้อย่างมีประโยชน์นะ




### Side-Effect

**แต่!** สำหรับการเขียนโปรแกรมเราสามารถเขียนอะไรได้มากกว่านั้น นั่นคือการทำให้ฟังก์ชันนั้นมี **"Side-Effect"** คือการที่ฟังก์ชันสามารถยุ่งกับค่าตัวแปรภายนอกได้ เช่น
```js
function displayPlusTen(x){
	print(x + 10)
}
```
นั่นคือเราเปลี่ยนจากการรีเทิร์นค่ากลับเป็นเอาค่าที่คำนวณได้ print ค่าออกมาแทน

![](https://4.bp.blogspot.com/-LexZSDSUUag/Xlan27RlxPI/AAAAAAAASJY/dEl3PyDV3bsfGI5Gto2guAQocqsBvj85QCNcBGAsYHQ/s1600/fp-01-1.png)

นอกจากนี้ Side-Effect ยังรวมถึงการรับ input เข้ามาจากผู้ใช้ หรือการเปลี่ยนแปลงค่าตัวแปรระดับ global ด้วย
```js
// Side-Effect จากการรับค่าจากผู้ใช้เข้ามา
function readX(){
  var x = input('enter x: ')
  ...
}

// Side-Effect จากการอ่านหรือเขียนค่าตัวแปรภายนอก
var count = 0
function increment(){
  count = count + 1
}
```
> Side-Effect จะเกิดขึ้นเมื่อฟังก์ชันมีการอ่าน/เขียนตัวแปรนอกฟังก์ชัน หรือมีการติดต่อกับ I/O (input หรือ output) ภายนอก ไม่ว่าจะเป็นการอ่านค่าจากผู้ใช้, การแสดงผลทางหน้าจอ, การอ่าน/เขียน Database, หรือแม้แต่การเชื่อมต่อ API

ปัญหาของ Side-Effect คือมันทำให้ฟังก์ชันไม่เพียว! พอฟังก์ชันนั้นไม่ใช่ Pure Function สิ่งที่ตามมาก็คือ state ที่มากมาย ยากต่อการเทสโปรแกรม

![](https://1.bp.blogspot.com/-41d2xweLEwM/XmO7-_MsqMI/AAAAAAAASKc/UqNyhL2VrnotchorilwjJwao8WzNqQoCwCNcBGAsYHQ/s1600/fp-01-2.png)

จากตัวอย่างข้างบน เราสร้างฟังก์ชัน `countIt()` ขึ้นมาเพื่อนับเลขไปเรื่อยๆ ทุกครั้งที่มีการเรียกใช้งาน ทีนี้ให้ลองดูที่โปรแกรมบรรทัด *(1)*, *(2)*, และ *(3)* เราจะพบว่าผลลัพธ์จากฟังก์ชัน `countIt()` นั้นเปลี่ยนไปเรื่อยๆ ตามตัวแปรภายนอกของโปรแกรม หรือที่เราเรียกว่า **State** ของโปรแกรมนั่นเอง

ถ้า state ของโปรแกรมในขณะนี้รันมาถึงจังหวะที่ `count=1` ฟังก์ชัน `countIt()` ก็จะเปลี่ยน state เป็น `count=2` แล้วตอบคำตอบเป็น 2

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

### แล้ว method ใน OOP ล่ะ?
สำหรับภาษายุคใหม่ๆ ที่ขาดไม่ได้เลยก็คือฟีเจอร์การเขียนโปรแกรมแบบ Object-Oriented นั่นเอง แล้วสำหรับภาษา OOP แท้ๆ เช่น Java เนี่ยจะกำหนดว่าทุกสิ่งทุกอย่างจะต้องอยู่ในรูปของ class เท่านั้น ไม่สามารถสร้างฟังก์ชันลอยๆ อยู่ข้างนอกได้

> ใน OOP ไม่มีฟังก์ชัน แต่จะเรียกว่า method แทน

ส่วนความแตกต่างระหว่าง **function** vs **method** ก็คือเมธอดสามารถใช้งานตัวแปรภายในของคลาสที่เราเรียกว่า properties ได้นั่นเอง

```js
class People {
  var name
  function setName(name){
    this.name = name
  }
  function sayHi(){
    return 'my name is ${name}'
  }
}
```
โดยส่วนใหญ่แล้ว method มักจะมีการ"ยุ่งเกี่ยว"กับตัวแปรภายนอกแบบนี้เสมอ ซึ่งไม่ใช่เรื่องผิดอะไรในโลกของ OOP แต่ถ้าในมุมของ FP แล้วนั้น ทำให้เมธอดส่วนใหญ่ไม่มีคุณสบมัติของ pure function ยังไงล่ะ

แล้วปัญหาของการที่เราไม่เขียนเมธอดให้เป็น pure function จะทำให้เกิดปัญหาอะไร?

ลองดูตัวอย่างต่อไป

```js
class WickedService {
  var x = 1
  function foo(){
    ...  
  }
  function displayX(){
    print(x) // 1
    foo()
    print(x) // ???
  }
}
```
ลองดูที่เมธอด `displayX()` จะเห็นว่ามีคำสั่งปริ๊นค่า `x` อยู่ สมมุติว่าครั้งแรกที่คำสั่งนี้ทำงานค่า `x` มีค่าเป็น **1**

ดังนั้นความคาดหวังของเราคือถ้าเราปริ๊นค่า `x` อีกทีนึง ค่าที่ได้ก็ควรจะเป็น **1** เหมือนเดิม เพราะในเมธอดนี้ไม่ได้มีการเซ็ตค่า `x` ใหม่เลย

แต่ก็ไม่ได้เป็นแบบนั้นทุกครั้ง! เพราะในระหว่างที่ `displayX()` ทำงานอยู่นั้น มีการเรียกใช้ `foo()` ขั้นกลางด้วย **และมีโอกาสที่เมธอด `foo()` จะทำการเซ็ตค่า `x` ใหม่ทำให้มันเปลี่ยนแปลงไประหว่างที่ `displayX()` กำลังทำงานอยู่**

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

### Purity คุณสมบัติแห่งความบริสุทธิ์
เพราะว่า pure function ไม่อนุญาตให้มี Side-Effect จากภายนอกมายุ่งกับโลจิคการทำงานภายในทั้นสิ้น มันเลยมีคุณสมบัติอีกอย่างนั่นคือ...

> ไม่เรียกใช้ฟังก์ชันกี่ครั้ง ถ้า**input**เหมือนเดิม **output**ก็ต้องเหมือนเดิมตามไปด้วย!

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

เช่น ถ้าเรามีฟังก์ชันตัวนึง ที่ใส่ค่า **6** เข้าไปแล้วมันตอบค่า **7** กลับมา แบบนี้
```js
f(6) => 7
```
ไม่ว่าเราจะเรียก `f(6)` อีกกี่ครั้ง มันก็จะต้องตอบ **7** อย่างแน่นอน
```js
f(6) => 7
f(6) => 7 ไงล่ะ!
f(6) => ยังคงเป็น 7
f(6) => ก็ยัง 7 อยู่นะ
```
คุณสมบัตินี้เรานี่แหละที่เราเรียกว่า **purity** หรือการ **memorize** คำตอบของฟังก์ชันไว้ได้ (จำคำตอบไว้ได้ เพราะไม่ว่าจะเรียกฟังก์ชันกี่รอบ มันก็ต้องได้คำตอบเดิม)

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

> Pure Function จะมีคุณสมบัติ **Purity** หรือการที่คำตอบจะต้องออกมาเหมือนเดิมทุกรอบ (ถ้า input เหมือนเดิม) ซึ่งจะนำมาสู่คุณสมบัติ **Memorize** ที่เป็นจดจำคำตอบไว้ ซึ่งจะมีประโยชน์มากหากฟังก์ชันนั้นมีการทำงานที่หนักกว่าจะได้คำตอบมา (เช่นฟังก์ชันที่ `O(n)` เยอะๆ )

> **📢 Note:**  ในวิชา Data Structure and Algorithm เรื่องนี้เป็นเรื่องเดียวกับแนวคิดของ **Dynamic Programming** นั่นเอง!

แต่ถ้าการเรียกใช้งานแต่ละครั้ง ให้ค่ากลับมาไม่เหมือนกัน แบบนี้
```js
f(6) => 7 //ครั้งแรกตอบแบบนี้
f(6) => 3 //ครั้งที่สองตอบ 3 แทน
f(6) => 5 //ต่อไปเปลี่ยนไปตอบ 5 !
```
แบบนี้ไม่ใช่ pure function ก็จะทำให้ไม่มีคุณสบมัติ purity ไปด้วย

---

## First-Class Function

ชื่อของหัวข้อนี้มาจากคำว่า *first class citizen* หรือประชากรชั้นหนึ่ง ไม่ใช่คนต่างชาติหรือต่างด้าว ก็คือประชาชนของประเทศนั่นแหละ ซึ่งก็จะมีสิทธิพื้นฐานคือทำได้ทุกอย่าง

ทีนี้ถ้าในมุมของการเขียนโปรแกรม การจะเป็นประชากรชั้นหนึ่งได้นั้นหมายความว่าเราจะต้องสร้าง type ชนิดนั้น**จะต้องเซ็ตเป็นตัวแปรได้** = **First-Class Function ก็คือการที่เราสามารถกำหนดให้ฟังก์ชันกลายเป็นตัวแปรได้นั่นเอง**
```js
function add(x,y){
  return x + y
}

// call function: ผลลัพธ์ก็จะได้ 1 + 2 = 3
var a = add(1, 2)

// กำหนดตัวแปร f ให้เท่ากับฟังก์ชัน, แต่ไม่ได้เป็นการรันฟังก์ชันนะ สังเกตดูว่าไม่มี () ต่อหลัง
var f = add
// ตัวแปร f ในตอนนี้มีค่าเป็นตัวฟังก์ชัน add นั่นเอง
var b = f(1, 2)
```
การสร้างฟังก์ชันแบบปกติจะประกอบด้วยการ declare คือการกำหนดว่าฟังก์ชันจะทำงานยังไง และการ call หรือการเรียกใช้งานฟังก์ชันที่สร้างขึ้นมา

แต่ก็มีข้อแตกต่างระหว่างการ declare ฟังก์ชันกับการสร้างฟังก์ชันใส่ตัวแปร 
นั่นคือการ declare ฟังก์ชันนั้น ส่วนใหญ่จะมีสโคปการใช้งานแบบ global แต่ถ้าใช้แบบตัวแปร ก็จะใช้กฎเดียวกับการสร้างตัวแปร นั่นคือก่อนเราสร้างตัวแปรเราจะใช้งานมันไม่ได้
```js
// ตรงนี้สามารถเรียกใช้ f() ได้
// แต่เรียกใช้ g() ไม่ได้เพราะยังไม่ถึงบรรทัดที่ประกาศตัวแปร

function f(){ ... }

var g = function(){ ... }

// ตั้งแต่ส่วนนี้ไป ถึงจะเรียกใช้ g() ได้
```
---

## High-Order Function
คุณสมบัตินี้จริงๆ เป็นคุณสมบัติที่ต่อเนื่องจาก First-Class Function นั่นคือเมื่อพอเรากำหนดฟังก์ชันเป็นตัวแปรได้ เราก็สามารถส่งฟังก์ชันผ่าน parameter ของฟังก์ชัน (อีกตัวหนึ่ง) หรือจะเป็นการรีเทิร์นฟังก์ชันกลับมาก็ยังได้
```js
function calculate(a, b, operator){
  return operator(a, b)
}

function add(x, y){
  return x + y
}
function sub(x, y){
  return x - y
}

calculate(2, 1, add) // 2 + 1 = 3
calculate(2, 1, sub) // 2 - 1 = 1
```
ตามตัวอย่างข้างบน เราสามารถสร้างฟังก์ชัน `calculate` ที่รับ**ฟังก์ชันอีกตัวหนึ่ง**เข้ามาเพื่อทำการโปรเซสค่า โดยที่ไม่ต้องรู้ว่ามันทำการคำนวณยังไง เราสามารถเปลี่ยนแปลงการคำนวณของ `calculate` ได้ง่ายๆ โดยการสร้างฟังก์ชันใหม่แล้วใส่เข้าไปกี่ตัวก็ได้ เช่น `add`, `sub` (คอนเซ็ปของ encapsulation ของฟังก์ชันไงล่ะ!)

หรือจะใช้ในอีกรูปแบบหนึ่งคือการรีเทิร์นฟังก์ชันกลับมาก็ได้เช่นกัน แบบนี้
```js
function priceCalculatorBuilder(type){
  if(type == NORMAL)    return function(unit){ return unit * 10 }
  if(type == DISCOUNT)  return function(unit){ return unit * 10 - 100 }
  if(type == FREE)      return function(unit){ return 0 }
}
```
สมมุติเราต้องการฟังก์ชันชื่อ `priceCalculator` เอาไว้คำนวณจำนวนเงิน แต่ปัญหาคือมีสูตรการคำนวณหลายแบบเหลือเกิน

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

สังเกตดูว่าสิ่งที่ฟังก์ชันรีเทิร์นกลับคือฟังก์ชันอีกตัวหนึ่ง นั่นหมายความว่า การเรียนใช้ง่านในครั้งแรกจะยังไม่ได้ค่าจำนวนเงินออกมา แต่จะเป็นฟังก์ชันที่เอาไว้คำนวณเงินแทน (ตาม type ที่ใส่เข้าไปว่าจะให้คำนวณแบบไหน)
```js
var priceCalculator = priceCalculatorBuilder(type)
print(priceCalculator(5))
```
อธิบายเพิ่มเติมเกี่ยวกับแนวคิดของฟังก์ชันในโค้ดนี้กันหน่อย

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


## สรุป
1. ความจริงคอนเซ็ปของ Pure Function นั้นง่ายมาก นั่นคือ input --> process --> output โดยห้ามมีค่าภายนอกหรือ I/O มาเขียนข้องเลย...แม้แต่นิดเดียวกันไม่ได้ !
2. ปัญหาคือตั้งแต่เราเริ่มเขียนโปรแกรมกัน เราไม่ค่อยได้สร้าง Pure Function กันเท่าไหร่ มักจะสร้างให้มันมี Side-Effect เล็กๆ น้อยๆ เสมอ (เอาง่ายๆ เช่นการ `print()` ไงล่ะ)
3. First Class Function คือการยอมให้เราเก็บฟังก์ชันลงในตัวแปรได้ **ซึ่งถือว่าเป็นข้อสำคัญ ที่หากเราจะเขียน FP แล้ว ภาษาโปรแกรมนั้นจะต้องมีคุณสมบัติข้อนี้**
4. High Order Function ไม่มีอะไรมาก พอฟังก์ชันกลายเป็นตัวแปรได้แล้ว มันก็สามารถถูกส่งไปผ่าน parameter หรือจะรีเทิร์นค่ากลับมาก็ยังได้

## Next~
ในบทต่อไป เราจะพูดกันต่อในเรื่องของการสร้างฟังก์ชันนิรนามและการปิดล้อม (Closure) ของฟังก์ชัน