คณิตศาสตร์ที่เรียนมา ไม่ได้เสียเปล่า — เรื่องของ Function Composition สู่ Pipe & Compose ใน JavaScript
สารบัญ
ตอน ม.3 ผมเรียนเรื่อง "ฟังก์ชันประกอบ" (Function Composition) ในวิชาคณิตศาสตร์ จำได้ว่าตอนนั้นรู้สึกว่ามันเป็นนามธรรมมาก สูตร (f ∘ g)(x) = f(g(x)) ดูเหมือนโจทย์ที่ไม่เห็นภาพว่าจะเอาไปใช้ทำอะไรในชีวิตจริง
ผ่านไปหลายปี ผมกลับมาเจอแนวคิดนี้อีกครั้งในการเขียนโปรแกรม JavaScript แบบ Functional แล้วผมก็อ๋อ — ฟังก์ชัน pipe และ compose ใน JavaScript ไม่ใช่อะไรอื่น มันคือฟังก์ชันประกอบในคณิตศาสตร์ที่ผมเคยเรียนนั่นเอง
บทความนี้เล่าเรื่องตั้งแต่ต้นว่า ความรู้คณิตศาสตร์ตอน ม.3 สามารถนำมาประยุกต์ใช้กับการเขียนโปรแกรมได้อย่างไร
ย้อนรอย: ฟังก์ชันประกอบในคณิตศาสตร์ ม.3
ในคณิตศาสตร์ ฟังก์ชันประกอบ (Function Composition) เขียนแทนด้วยสัญลักษณ์วงกลม:
(f ∘ g)(x) = f(g(x))
แปลว่า เอาฟังก์ชัน g มาทำกับ x ก่อน แล้วเอา f มาทำกับผลลัพธ์อีกที ลำดับคือ g ก่อน f
ตัวอย่างง่ายๆ:
f(x) = x + 1
g(x) = x * 2
(f ∘ g)(3) = f(g(3)) = f(2 * 3) = f(6) = 6 + 1 = 7
ตอนเรียนเรื่องนี้ ผมจำได้ว่าครูสอนแบบนี้ — แต่ผมก็ยังงงอยู่ดีว่า "แล้วมันเอาไปทำอะไรต่อ?" คำตอบของครูคือ "ใช้ในเรื่องแคลคูลัสและทฤษฎีฟังก์ชัน" ซึ่งตอนนั้นผมไม่เข้าใจว่ามันเกี่ยวกับชีวิตจริงยังไง
นานมาแล้ว: Unix Pipe (1970s)
หลายปีต่อมา ผมเริ่มเขียนโปรแกรมแล้วก็เจอ Unix Pipe ซึ่งเป็นแนวคิดที่ Doug McIlroy คิดขึ้นในช่วงทศวรรษ 1970 แนวคิดคือการต่อท่อข้อมูลระหว่างโปรแกรม
cat file.txt | grep "error" | sort
เครื่องหมาย | คือ "pipe" — ส่ง output ของโปรแกรมหนึ่งไปเป็น input ของอีกโปรแกรม ทำงานจากซ้ายไปขวา
ตอนนั้นผมยังไม่รู้ตัวด้วยซ้ำว่า นี่คือหลักการเดียวกับฟังก์ชันประกอบในคณิตศาสตร์ที่เรียนตอน ม.3
ข้ามมาเจอใน JavaScript
พอเริ่มเรียนรู้ Functional Programming ใน JavaScript ผมเจอฟังก์ชันสองตัวที่ทำให้เข้าใจเรื่องนี้ชัดเจนมาก:
Pipe — ทำงานจากซ้ายไปขวา
const pipe = (...fns) => (arg) =>
fns.reduce((acc, fn) => fn(acc), arg);
ใช้ reduce เพื่อทำงานจากซ้ายไปขวา เอาผลลัพธ์ของฟังก์ชันก่อนหน้า ไปเป็น input ของฟังก์ชันถัดไป
ตัวอย่างการใช้:
const trim = (str) => str.trim();
const toUpperCase = (str) => str.toUpperCase();
const exclaim = (str) => `${str}!`;
const pipedFunction = pipe(trim, toUpperCase, exclaim);
console.log(pipedFunction(' hello world ')); // "HELLO WORLD!"
ลำดับการทำงาน:
trim(' hello world ')→"hello world"toUpperCase("hello world")→"HELLO WORLD"exclaim("HELLO WORLD")→"HELLO WORLD!"
Compose — ทำงานจากขวาไปซ้าย
const compose = (...fns) => (arg) =>
fns.reduceRight((acc, fn) => fn(acc), arg);
ใช้ reduceRight แทน reduce เพื่อทำงานจากขวาไปซ้าย
const composedFunction = compose(exclaim, toUpperCase, trim);
console.log(composedFunction(' hello world ')); // "HELLO WORLD!"
ลำดับการทำงาน:
trim(' hello world ')→"hello world"toUpperCase("hello world")→"HELLO WORLD"exclaim("HELLO WORLD")→"HELLO WORLD!"
เปรียบเทียบ pipe vs compose
| หัวข้อ | pipe | compose |
|---|---|---|
| ลำดับการทำงาน | ซ้ายไปขวา | ขวาไปซ้าย |
| ใช้เมธอด | reduce | reduceRight |
| ความเข้าใจง่าย | อ่านง่ายกว่า | อ่านยากกว่า |
| ใช้ในไลบรารี | RxJS, Lodash | Redux, Ramda |
ผมชอบ pipe มากกว่าเพราะอ่านง่ายกว่า ลำดับที่เห็นในโค้ดตรงกับลำดับการทำงานจริง
ใช้จริงในงานยังไง
หลังจากเข้าใจ pipe กับ compose แล้ว ผมเริ่มเอาไปใช้ในงานจริงหลายที่:
1. Data Processing Pipeline
รับข้อมูลจาก API → ตรวจสอบ → แปลงรูปแบบ → บันทึกลงฐานข้อมูล
const processOrder = pipe(
validateInput,
normalizeData,
calculateTotal,
saveToDatabase,
sendConfirmation
);
2. Redux Middleware
Redux ใช้ compose ในการต่อ middleware เข้าด้วยกัน
const enhancer = compose(
applyMiddleware(thunk, logger),
devToolsExtension
);
3. API Request Validation
ตรวจสอบ token → ดึงข้อมูล → แปลงผลลัพธ์ → ส่งกลับ
const fetchUserData = pipe(
validateToken,
callUserApi,
transformResponse,
cacheResult
);
4. Form Validation
ตรวจสอบหลายเงื่อนไขเป็นลำดับ
const validateForm = pipe(
checkRequiredFields,
checkEmailFormat,
checkPasswordStrength,
checkDuplicateUser
);
Note: เมื่อไหร่ควรใช้ pipe — เมื่อต้องการเห็นลำดับการทำงานชัดเจน เช่น data pipeline หรือ form validation. เมื่อไหร่ควรใช้ compose — เมื่อต้องการให้ทำงานในรูปแบบของ math composition เช่น Redux middleware หรือ functional composition ทั่วไป.
บทสรุป
คณิตศาสตร์ที่เรียนตอน ม.3 ไม่ได้เสียเปล่าครับ ฟังก์ชันประกอบ (Function Composition) ที่ดูเป็นนามธรรมในตอนนั้น กลับกลายเป็นหลักการสำคัญของ Functional Programming ที่ใช้ในการเขียนโปรแกรมจริงในปัจจุบัน
Unix Pipe ในปี 1970 → ฟังก์ชัน compose/pipe ใน JavaScript → Redux Middleware → Data Pipeline ในงานจริง — ทั้งหมดนี้มีรากฐานมาจากแนวคิดเดียวกัน
ผมว่าสิ่งที่เรียนมา ไม่มีอะไรที่เสียเปล่าจริงๆ แม้ตอนเรียนจะรู้สึกว่าไม่เห็นภาพ แต่พอเวลาผ่านไป เราจะเห็นว่ามันต่อยอดได้ในหลายทาง
อ้างอิง
- MDN: Array.prototype.reduce — พื้นฐานของ reduce ที่ใช้ใน pipe
- MDN: Array.prototype.reduceRight — พื้นฐานของ reduceRight ที่ใช้ใน compose
- GeeksforGeeks: pipe and compose in JavaScript — ตัวอย่างการใช้งานเพิ่มเติม
- Wikipedia: Function composition — ประวัติและทฤษฎีทางคณิตศาสตร์
- History of the function concept — ประวัติของแนวคิดฟังก์ชัน
- Unix pipe history — ประวัติของ Unix pipe โดย Doug McIlroy
เนื้อหานี้มีประโยชน์ไหม? ช่วยสนับสนุนค่ากาแฟให้ผู้เขียนสักแก้ว
Buy Me a Coffee