บทนำสู่การเขียนโปรแกรมเชิงฟังก์ชัน: กระบวนทัศน์ JavaScript
เผยแพร่แล้ว: 2022-03-11การเขียนโปรแกรมเชิงฟังก์ชันเป็นกระบวนทัศน์ของการสร้างโปรแกรมคอมพิวเตอร์โดยใช้นิพจน์และฟังก์ชันโดยไม่ทำให้สถานะและข้อมูลกลายพันธุ์
โดยการเคารพข้อจำกัดเหล่านี้ การเขียนโปรแกรมเชิงฟังก์ชันมีจุดมุ่งหมายเพื่อเขียนโค้ดที่เข้าใจได้ชัดเจนขึ้นและสามารถต้านทานข้อผิดพลาดได้มากขึ้น สิ่งนี้ทำได้โดยหลีกเลี่ยงการใช้คำสั่งควบคุมการไหล ( for
, while
, break
, continue
, goto
) ซึ่งทำให้โค้ดติดตามยากขึ้น นอกจากนี้ ฟังก์ชันโปรแกรมมิ่งยังต้องการให้เราเขียนฟังก์ชันที่บริสุทธิ์และกำหนดขึ้นเองได้ ซึ่งมีโอกาสน้อยที่จะเกิดข้อผิดพลาด
ในบทความนี้ เราจะพูดถึงการเขียนโปรแกรมเชิงฟังก์ชันโดยใช้ JavaScript เราจะสำรวจวิธีการและคุณลักษณะต่างๆ ของ JavaScript ที่ทำให้เป็นไปได้ด้วย ในตอนท้าย เราจะสำรวจแนวคิดต่างๆ ที่เกี่ยวข้องกับการเขียนโปรแกรมเชิงฟังก์ชัน และดูว่าเหตุใดจึงมีประสิทธิภาพมาก
ก่อนที่จะเข้าสู่การเขียนโปรแกรมเชิงฟังก์ชัน เราต้องเข้าใจความแตกต่างระหว่างฟังก์ชันบริสุทธิ์และฟังก์ชันที่ไม่บริสุทธิ์
ฟังก์ชันที่บริสุทธิ์ vs. ที่ไม่บริสุทธิ์
ฟังก์ชันบริสุทธิ์รับอินพุตบางส่วนและให้เอาต์พุตคงที่ นอกจากนี้ยังไม่ก่อให้เกิดผลข้างเคียงในโลกภายนอก
const add = (a, b) => a + b;
ที่นี่ add
เป็นฟังก์ชันบริสุทธิ์ เนื่องจากสำหรับค่าคงที่ของ a
และ b
ผลลัพธ์จะเหมือนกันเสมอ
const SECRET = 42; const getId = (a) => SECRET * a;
getId
ไม่ใช่ฟังก์ชันบริสุทธิ์ เหตุผลก็คือมันใช้ตัวแปรส่วนกลาง SECRET
ในการคำนวณเอาท์พุต หากต้องเปลี่ยน SECRET
ฟังก์ชัน getId
จะคืนค่าที่ต่างกันสำหรับอินพุตเดียวกัน ดังนั้นจึงไม่ใช่หน้าที่บริสุทธิ์
let id_count = 0; const getId = () => ++id_count;
นี่เป็นฟังก์ชันที่ไม่บริสุทธิ์ และด้วยเหตุผลสองสามประการ (1) ใช้ตัวแปรที่ไม่ใช่ในเครื่องเพื่อคำนวณผลลัพธ์ และ (2) สร้างผลข้างเคียงในโลกภายนอกโดยการปรับเปลี่ยนตัวแปรในนั้น โลก.
นี่อาจเป็นปัญหาหากเราต้องดีบั๊กโค้ดนี้
มูลค่าปัจจุบันของ id_count
คืออะไร? ฟังก์ชั่นอื่นใดที่แก้ไข id_count
? มีฟังก์ชั่นอื่นที่ใช้ id_count
หรือไม่
ด้วยเหตุผลเหล่านี้ เราจึงใช้ฟังก์ชันบริสุทธิ์ในการเขียนโปรแกรมเชิงฟังก์ชันเท่านั้น
ประโยชน์อีกประการของฟังก์ชันบริสุทธิ์คือสามารถขนานและบันทึกได้ มาดูสองฟังก์ชั่นก่อนหน้ากัน เป็นไปไม่ได้ที่จะขนานหรือจดจำไว้ ซึ่งช่วยในการสร้างรหัสการปฏิบัติงาน
หลักการของ Functional Programming
จนถึงตอนนี้ เราได้เรียนรู้ว่าการเขียนโปรแกรมเชิงฟังก์ชันนั้นขึ้นอยู่กับกฎสองสามข้อ พวกเขามีดังนี้
- อย่าเปลี่ยนข้อมูล
- ใช้ฟังก์ชันล้วนๆ: เอาต์พุตคงที่สำหรับอินพุตคงที่ และไม่มีผลข้างเคียง
- ใช้นิพจน์และการประกาศ
เมื่อเราปฏิบัติตามเงื่อนไขเหล่านี้ เราสามารถพูดได้ว่าโค้ดของเราใช้งานได้
การเขียนโปรแกรมเชิงฟังก์ชันใน JavaScript
JavaScript มีฟังก์ชันบางอย่างที่เปิดใช้งานการเขียนโปรแกรมเชิงฟังก์ชันอยู่แล้ว ตัวอย่าง: String.prototype.slice, Array.protoype.filter, Array.prototype.join
ในทางกลับกัน Array.prototype.forEach, Array.prototype.push เป็นฟังก์ชันที่ไม่บริสุทธิ์
อาจมีคนโต้แย้งว่า Array.prototype.forEach
ไม่ใช่ฟังก์ชันที่ไม่บริสุทธิ์จากการออกแบบ แต่ลองคิดดู—มันเป็นไปไม่ได้ที่จะทำอะไรกับมัน ยกเว้นการกลายพันธุ์ของข้อมูลที่ไม่ใช่ในเครื่องหรือทำผลข้างเคียง ดังนั้นจึงเป็นเรื่องปกติที่จะจัดหมวดหมู่ฟังก์ชันที่ไม่บริสุทธิ์
นอกจากนี้ JavaScript ยังมีการประกาศ const ซึ่งเหมาะสำหรับการเขียนโปรแกรมเชิงฟังก์ชัน เนื่องจากเราจะไม่เปลี่ยนแปลงข้อมูลใดๆ
ฟังก์ชั่นบริสุทธิ์ใน JavaScript
มาดูฟังก์ชันบริสุทธิ์ (เมธอด) ที่จาวาสคริปต์ให้มา
กรอง
ตามชื่อที่แนะนำ สิ่งนี้จะกรองอาร์เรย์
array.filter(condition);
เงื่อนไขที่นี่คือฟังก์ชันที่รับแต่ละรายการของอาร์เรย์ และควรตัดสินใจว่าจะเก็บรายการนั้นไว้หรือไม่ และส่งคืนค่าบูลีนที่เป็นจริงสำหรับสิ่งนั้น
const filterEven = x => x%2 === 0; [1, 2, 3].filter(filterEven); // [2]
ขอให้สังเกตว่า filterEven
เป็นฟังก์ชันที่บริสุทธิ์ ถ้ามันเป็นสิ่งที่ไม่บริสุทธิ์ ก็จะทำให้การเรียกตัวกรองทั้งหมดไม่บริสุทธิ์
แผนที่
map
จะจับคู่แต่ละรายการของอาร์เรย์กับฟังก์ชันหนึ่งๆ และสร้างอาร์เรย์ใหม่ตามค่าที่ส่งคืนของการเรียกใช้ฟังก์ชัน
array.map(mapper)
mapper
เป็นฟังก์ชันที่รับรายการของอาร์เรย์เป็นอินพุตและส่งคืนเอาต์พุต
const double = x => 2 * x; [1, 2, 3].map(double); // [2, 4, 6]
ลด
reduce
ลดอาร์เรย์เป็นค่าเดียว
array.reduce(reducer);
reducer
คือฟังก์ชันที่ใช้ค่าสะสมและรายการถัดไปในอาร์เรย์และส่งกลับค่าใหม่ มันถูกเรียกแบบนี้สำหรับค่าทั้งหมดในอาร์เรย์ ทีละค่า
const sum = (accumulatedSum, arrayItem) => accumulatedSum + arrayItem [1, 2, 3].reduce(sum); // 6
คอนแคท
concat
เพิ่มรายการใหม่ให้กับอาร์เรย์ที่มีอยู่เพื่อสร้างอาร์เรย์ใหม่ มันแตกต่างจาก push()
ในแง่ที่ push()
กลายพันธุ์ข้อมูลซึ่งทำให้ไม่บริสุทธิ์

[1, 2].concat([3, 4]) // [1, 2, 3, 4]
คุณยังสามารถทำเช่นเดียวกันนี้โดยใช้ตัวดำเนินการสเปรด
[1, 2, ...[3, 4]]
Object.assign
Object.assign
คัดลอกค่าจากอ็อบเจ็กต์ที่จัดเตรียมไปยังอ็อบเจ็กต์ใหม่ เนื่องจากการเขียนโปรแกรมเชิงฟังก์ชันถูกกำหนดด้วยข้อมูลที่ไม่เปลี่ยนรูปแบบ เราจึงใช้มันเพื่อสร้างวัตถุใหม่โดยยึดตามวัตถุที่มีอยู่
const obj = {a : 2}; const newObj = Object.assign({}, obj); newObj.a = 3; obj.a; // 2
ด้วยการถือกำเนิดของ ES6 สิ่งนี้สามารถทำได้โดยใช้ตัวดำเนินการสเปรด
const newObj = {...obj};
การสร้างฟังก์ชันที่บริสุทธิ์ของคุณเอง
เราสามารถสร้างฟังก์ชันที่บริสุทธิ์ของเราได้เช่นกัน ลองทำหนึ่งสำหรับการทำซ้ำสตริง n
จำนวนครั้ง
const duplicate = (str, n) => n < 1 ? '' : str + duplicate(str, n-1);
ฟังก์ชันนี้ทำซ้ำสตริง n
ครั้งและส่งคืนสตริงใหม่
duplicate('hooray!', 3) // hooray!hooray!hooray!
ฟังก์ชั่นการสั่งซื้อที่สูงขึ้น
ฟังก์ชันลำดับที่สูงกว่าคือฟังก์ชันที่รับฟังก์ชันเป็นอาร์กิวเมนต์และส่งกลับฟังก์ชัน มักใช้เพื่อเพิ่มฟังก์ชันการทำงานของฟังก์ชัน
const withLog = (fn) => { return (...args) => { console.log(`calling ${fn.name}`); return fn(...args); }; };
ในตัวอย่างข้างต้น เราสร้างฟังก์ชันที่มีลำดับสูงกว่า withLog
ที่รับฟังก์ชันและส่งกลับฟังก์ชันที่บันทึกข้อความก่อนที่ฟังก์ชันที่ห่อหุ้มจะทำงาน
const add = (a, b) => a + b; const addWithLogging = withLog(add); addWithLogging(3, 4); // calling add // 7
withLog
HOF สามารถใช้กับฟังก์ชันอื่นๆ ได้เช่นกัน และทำงานได้โดยไม่มีข้อขัดแย้งหรือเขียนโค้ดเพิ่มเติม นี่คือความงามของ HOF
const addWithLogging = withLog(add); const hype = s => s + '!!!'; const hypeWithLogging = withLog(hype); hypeWithLogging('Sale'); // calling hype // Sale!!!
เราสามารถเรียกมันได้โดยไม่ต้องกำหนดฟังก์ชันการรวม
withLog(hype)('Sale'); // calling hype // Sale!!!
แกงกะหรี่
Currying หมายถึงการแยกฟังก์ชันที่รับอาร์กิวเมนต์หลายตัวเป็นฟังก์ชันลำดับที่สูงกว่าหนึ่งระดับหรือหลายระดับ
ลองใช้ฟังก์ชัน add
กัน
const add = (a, b) => a + b;
เมื่อเราต้องการจะอธิบาย เราเขียนใหม่โดยกระจายข้อโต้แย้งออกเป็นหลายระดับดังนี้
const add = a => { return b => { return a + b; }; }; add(3)(4); // 7
ประโยชน์ของการแกงคือการท่องจำ ขณะนี้ เราสามารถจดจำอาร์กิวเมนต์บางอย่างในการเรียกใช้ฟังก์ชันเพื่อให้สามารถนำมาใช้ซ้ำได้ในภายหลังโดยไม่ต้องทำซ้ำและคำนวณใหม่
// assume getOffsetNumer() call is expensive const addOffset = add(getOffsetNumber()); addOffset(4); // 4 + getOffsetNumber() addOffset(6);
สิ่งนี้ดีกว่าการใช้อาร์กิวเมนต์ทั้งสองทุกที่อย่างแน่นอน
// (X) DON"T DO THIS add(4, getOffsetNumber()); add(6, getOffsetNumber()); add(10, getOffsetNumber());
นอกจากนี้เรายังสามารถฟอร์แมตฟังก์ชัน curried ของเราใหม่เพื่อให้ดูกระชับ เนื่องจากแต่ละระดับของการเรียกใช้ฟังก์ชัน currying เป็นคำสั่งส่งคืนบรรทัดเดียว ดังนั้นเราจึงสามารถใช้ฟังก์ชันลูกศรใน ES6 เพื่อปรับโครงสร้างใหม่ได้ดังนี้
const add = a => b => a + b;
องค์ประกอบ
ในวิชาคณิตศาสตร์ องค์ประกอบถูกกำหนดให้ส่งผ่านเอาต์พุตของฟังก์ชันหนึ่งไปยังอินพุตของฟังก์ชันอื่นเพื่อสร้างเอาต์พุตแบบรวม เป็นไปได้เช่นเดียวกันในการเขียนโปรแกรมเชิงฟังก์ชันเนื่องจากเราใช้ฟังก์ชันบริสุทธิ์
เพื่อแสดงตัวอย่าง มาสร้างฟังก์ชันบางอย่างกัน
ฟังก์ชันแรกคือ range ซึ่งใช้หมายเลขเริ่มต้น a
และหมายเลขสิ้นสุด b
และสร้างอาร์เรย์ที่ประกอบด้วยตัวเลขจาก a
ถึง b
const range = (a, b) => a > b ? [] : [a, ...range(a+1, b)];
จากนั้นเรามีฟังก์ชันคูณซึ่งรับอาร์เรย์และคูณตัวเลขทั้งหมดในนั้น
const multiply = arr => arr.reduce((p, a) => p * a);
เราจะใช้ฟังก์ชันเหล่านี้ร่วมกันเพื่อคำนวณแฟกทอเรียล
const factorial = n => multiply(range(1, n)); factorial(5); // 120 factorial(6); // 720
ฟังก์ชันข้างต้นสำหรับการคำนวณแฟกทอเรียลคล้ายกับ f(x) = g(h(x))
ซึ่งแสดงให้เห็นถึงคุณสมบัติขององค์ประกอบ
คำลงท้าย
เราได้ดำเนินการผ่านฟังก์ชันที่บริสุทธิ์และไม่บริสุทธิ์ การเขียนโปรแกรมเชิงฟังก์ชัน คุณลักษณะ JavaScript ใหม่ที่ช่วยในเรื่องนี้ และแนวคิดหลักสองสามประการในการเขียนโปรแกรมเชิงฟังก์ชัน
เราหวังว่างานชิ้นนี้จะกระตุ้นความสนใจของคุณในการเขียนโปรแกรมเชิงฟังก์ชันและอาจกระตุ้นให้คุณลองใช้โค้ดนี้ เรามั่นใจว่ามันจะเป็นประสบการณ์การเรียนรู้และเป็นก้าวสำคัญในเส้นทางการพัฒนาซอฟต์แวร์ของคุณ
การเขียนโปรแกรมเชิงฟังก์ชันเป็นกระบวนทัศน์การเขียนโปรแกรมคอมพิวเตอร์ที่มีการวิจัยอย่างดีและมีประสิทธิภาพ ด้วยการเปิดตัว ES6 นั้น JavaScript ช่วยให้มีประสบการณ์การเขียนโปรแกรมการทำงานที่ดีขึ้นกว่าเดิมมาก