Skip to main content

คณิตศาสตร์ที่เรียนมา ไม่ได้เสียเปล่า — เรื่องของ Function Composition สู่ Pipe & Compose ใน JavaScript

· 5 min read

ตอน ม.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!"

ลำดับการทำงาน:

  1. trim(' hello world ') → "hello world"
  2. toUpperCase("hello world") → "HELLO WORLD"
  3. 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!"

ลำดับการทำงาน:

  1. trim(' hello world ') → "hello world"
  2. toUpperCase("hello world") → "HELLO WORLD"
  3. exclaim("HELLO WORLD") → "HELLO WORLD!"

เปรียบเทียบ pipe vs compose​

หัวข้อpipecompose
ลำดับการทำงานซ้ายไปขวาขวาไปซ้าย
ใช้เมธอดreducereduceRight
ความเข้าใจง่ายอ่านง่ายกว่าอ่านยากกว่า
ใช้ในไลบรารีRxJS, LodashRedux, 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 ในงานจริง — ทั้งหมดนี้มีรากฐานมาจากแนวคิดเดียวกัน

ผมว่าสิ่งที่เรียนมา ไม่มีอะไรที่เสียเปล่าจริงๆ แม้ตอนเรียนจะรู้สึกว่าไม่เห็นภาพ แต่พอเวลาผ่านไป เราจะเห็นว่ามันต่อยอดได้ในหลายทาง

อ้างอิง​

แชร์บทความ
☕

เนื้อหานี้มีประโยชน์ไหม? ช่วยสนับสนุนค่ากาแฟให้ผู้เขียนสักแก้ว

Buy Me a Coffee
Loading...