JavaScript
Advanced Patterns
Functional programming basics
เรียนรู้แนวคิด Functional Programming — pure function, side effect และการประกอบฟังก์ชันเล็กๆ เข้าด้วยกัน เพื่อเขียนโค้ดที่คาดเดาได้และทดสอบง่าย
Functional Programming คืออะไร?
Functional Programming (FP) คือ **รูปแบบการเขียนโปรแกรม** (programming paradigm) ที่มองทุกอย่างเป็น "การประมวลผลผ่านฟังก์ชัน" แทนที่จะเปลี่ยนค่าตัวแปรทีละขั้น FP ไม่ใช่ library หรือ framework — มันคือ **สไตล์การคิดและเขียนโค้ด** ที่เน้นสามเรื่องหลัก: - เขียนฟังก์ชันที่ **คาดเดาผลลัพธ์ได้** (pure functions) - **หลีกเลี่ยงการเปลี่ยนสถานะ** ภายนอก (avoiding side effects) - **ประกอบฟังก์ชันเล็กๆ** เข้าด้วยกันเพื่อแก้ปัญหาใหญ่ (function composition)
| แนวทาง | Imperative (สั่งทีละขั้น) | Functional (ประมวลผลผ่านฟังก์ชัน) |
|---|---|---|
| แนวคิดหลัก | บอกว่าจะ "ทำอะไร" ทีละขั้น | บอกว่าข้อมูลจะ "แปลงเป็นอะไร" |
| สถานะ | เปลี่ยนตัวแปรได้ตลอด | หลีกเลี่ยงการเปลี่ยนสถานะ |
| ฟังก์ชัน | เป็นเพียงกลุ่มคำสั่ง | เป็นหน่วยหลักของการคำนวณ |
| ผลลัพธ์ | อาจขึ้นกับสถานะภายนอก | ขึ้นกับ input เท่านั้น |
const prices = [100, 200, 50, 75];
// แบบ Imperative — บอกทีละขั้น เปลี่ยน total โดยตรง
let total = 0;
for (let i = 0; i < prices.length; i++) {
total += prices[i];
}
console.log(total); // 425
// แบบ Functional — ส่งผ่านฟังก์ชัน ไม่แตะ total ข้างนอก
const sum = (acc, price) => acc + price;
const total2 = prices.reduce(sum, 0);
console.log(total2); // 425Pure Function — ฟังก์ชันที่เชื่อถือได้
Pure function คือฟังก์ชันที่มีสองคุณสมบัติ: 1. **Same input → same output** — ส่ง input เดิมเข้าไปกี่ครั้ง ได้ผลลัพธ์เดิมเสมอ 2. **No side effects** — ไม่แตะ หรือเปลี่ยนแปลงสิ่งใดนอก scope ของตัวเอง
// Pure function ✅
// - input เดิม → output เดิมเสมอ
// - ไม่แตะตัวแปรใดนอก scope
function add(a, b) {
return a + b;
}
console.log(add(3, 4)); // 7
console.log(add(3, 4)); // 7 — เสมอ
// Pure function ✅
function formatName(first, last) {
return `${first} ${last}`;
}
console.log(formatName("สมชาย", "ใจดี")); // "สมชาย ใจดี"
console.log(formatName("สมชาย", "ใจดี")); // "สมชาย ใจดี" — เสมอ// Impure function ❌ — ผลลัพธ์ขึ้นกับตัวแปรภายนอก
let taxRate = 0.07;
function calculatePrice(price) {
return price + price * taxRate; // อ่านจาก taxRate ข้างนอก!
}
console.log(calculatePrice(100)); // 107
taxRate = 0.10; // เปลี่ยน taxRate
console.log(calculatePrice(100)); // 110 — ผลต่างกันทั้งที่ input เหมือนกัน!
// Pure version ✅
function calculatePricePure(price, taxRate) {
return price + price * taxRate; // ทุกอย่างมาจาก parameter
}
console.log(calculatePricePure(100, 0.07)); // 107
console.log(calculatePricePure(100, 0.07)); // 107 — เสมอ- ทดสอบง่าย — แค่ส่ง input ตรวจ output ไม่ต้อง mock สถานะภายนอก
- debug ง่าย — ถ้าพบบั๊กใน pure function ปัญหาอยู่แค่ใน input/logic ของฟังก์ชันนั้น
- ใช้ซ้ำได้ปลอดภัย — เรียกที่ไหนก็ไม่กระทบสิ่งอื่น
Side Effects — ผลข้างเคียงที่ต้องระวัง
Side effect คือ **ทุกสิ่งที่ฟังก์ชันทำนอกเหนือจากการ return ค่า** — ไม่ว่าจะเป็นการเปลี่ยนตัวแปรภายนอก, เขียนไฟล์, แสดงผลบนหน้าจอ หรือเรียก API Side effect ไม่ใช่สิ่งผิด — โปรแกรมที่ทำงานได้จริงต้องมี side effect บ้าง (เช่น แสดงผลให้ผู้ใช้เห็น) แต่ FP สอนให้ **แยก side effect ออกจาก logic หลัก** ให้ชัดเจน
| ประเภท Side Effect | ตัวอย่าง |
|---|---|
| เปลี่ยนตัวแปรภายนอก | การแก้ไขตัวแปร global หรือตัวแปรใน outer scope |
| เปลี่ยน object/array ที่รับมา | การ push เข้า array, แก้ไข property ของ object parameter |
| I/O operations | console.log, alert, เขียนไฟล์, fetch API |
| เปลี่ยน DOM | document.getElementById(...).textContent = ... |
| เรียก external service | การส่ง HTTP request, บันทึก database |
// Side effect: เปลี่ยน array ที่รับมา (mutation) ❌
function addItem(cart, item) {
cart.push(item); // แก้ไข array ต้นฉบับ!
return cart;
}
const myCart = ["ข้าว", "น้ำ"];
addItem(myCart, "ไข่");
console.log(myCart); // ["ข้าว", "น้ำ", "ไข่"] — myCart เปลี่ยนไปแล้ว!
// Pure version: สร้าง array ใหม่ ไม่แตะต้นฉบับ ✅
function addItemPure(cart, item) {
return [...cart, item]; // สร้าง array ใหม่
}
const myCart2 = ["ข้าว", "น้ำ"];
const newCart = addItemPure(myCart2, "ไข่");
console.log(myCart2); // ["ข้าว", "น้ำ"] — ไม่เปลี่ยน!
console.log(newCart); // ["ข้าว", "น้ำ", "ไข่"]Functions as First-Class Values — ฟังก์ชันเป็นค่า
ใน JavaScript ฟังก์ชันเป็น "first-class citizen" — ปฏิบัติเหมือนค่าธรรมดา (number, string) ได้ทุกอย่าง: **เก็บในตัวแปร, ส่งเป็น argument, หรือ return ออกจากฟังก์ชันอื่น** แนวคิดนี้เคยเรียนในบท Function Expression และ Closure — ในบริบทของ FP มันคือ **รากฐานสำคัญ** ที่ทำให้ทุกเทคนิค FP ทำงานได้
// เก็บฟังก์ชันในตัวแปร
const double = (n) => n * 2;
const square = (n) => n * n;
// ส่งฟังก์ชันเป็น argument
function applyTwice(fn, value) {
return fn(fn(value));
}
console.log(applyTwice(double, 3)); // double(double(3)) → double(6) → 12
console.log(applyTwice(square, 2)); // square(square(2)) → square(4) → 16
// Return ฟังก์ชันออกจากฟังก์ชัน (ใช้หลักการ Closure)
function makeGreeter(greeting) {
return (name) => `${greeting}, ${name}!`;
}
const sayHello = makeGreeter("สวัสดี");
console.log(sayHello("สมชาย")); // "สวัสดี, สมชาย!"
console.log(sayHello("สมหญิง")); // "สวัสดี, สมหญิง!"- ฟังก์ชันเก็บในตัวแปรได้ — `const fn = () => ...`
- ฟังก์ชันส่งเป็น argument ได้ — เรียกว่า "higher-order function"
- ฟังก์ชัน return ฟังก์ชันได้ — ใช้หลักการ closure ที่เรียนไปแล้ว
- คุณสมบัตินี้คือสิ่งที่ทำให้ function composition เป็นไปได้
Function Composition — ประกอบฟังก์ชันเล็กๆ
Function Composition คือการ **นำ output ของฟังก์ชันหนึ่งไปเป็น input ของอีกฟังก์ชัน** — ประกอบฟังก์ชันเล็กๆ ที่ทำหน้าที่ชัดเจนเข้าด้วยกัน แทนที่จะเขียนฟังก์ชันใหญ่ฟังก์ชันเดียวที่ทำทุกอย่าง แนวคิด: `compose(f, g)(x)` เท่ากับ `f(g(x))` — เรียก g ก่อน แล้วส่งผลให้ f
// ฟังก์ชันเล็กๆ แต่ละตัวทำหน้าที่เดียว
const trim = (str) => str.trim();
const toLowerCase = (str) => str.toLowerCase();
const removeSpaces = (str) => str.replace(/\s+/g, "-");
// ใช้แบบ manual composition — ซ้อนกัน
const slug1 = removeSpaces(toLowerCase(trim(" Hello World ")));
console.log(slug1); // "hello-world"
// สร้าง compose function เพื่อประกอบฟังก์ชัน
function compose(f, g) {
return (x) => f(g(x));
}
const trimAndLower = compose(toLowerCase, trim);
console.log(trimAndLower(" Hello World ")); // "hello world"
const toSlug = compose(removeSpaces, trimAndLower);
console.log(toSlug(" Hello World ")); // "hello-world"- `trim(" Hello World ")` → `"Hello World"` — ตัดช่องว่างหัวท้าย
- `toLowerCase("Hello World")` → `"hello world"` — แปลงเป็นตัวเล็ก
- `removeSpaces("hello world")` → `"hello-world"` — แทนที่ space ด้วย dash
- ฟังก์ชันแต่ละตัว pure — ทดสอบแยกกันได้, ประกอบในลำดับต่างกันได้
ข้อดีของ composition: แต่ละฟังก์ชันทำหน้าที่เดียว ทดสอบง่าย และนำมาประกอบใหม่ในลำดับต่างๆ ได้โดยไม่ต้องแก้ไข logic ภายใน
สรุป
- Functional Programming คือรูปแบบการคิดและเขียนโค้ด ไม่ใช่ library หรือ framework
- Pure function: input เดิม → output เดิมเสมอ, ไม่มี side effect
- Side effect คือทุกอย่างที่ฟังก์ชันทำนอกเหนือจากการ return — ต้องแยกออกจาก logic หลัก
- ฟังก์ชันเป็น first-class value — ส่งเป็น argument, return ออกมา, เก็บในตัวแปรได้
- Function Composition: นำ output ของฟังก์ชันหนึ่งไปเป็น input ของอีกฟังก์ชัน — ประกอบฟังก์ชันเล็กๆ เข้าด้วยกัน