บทนำสู่การเขียนโปรแกรมเชิงฟังก์ชัน: กระบวนทัศน์ 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) สร้างผลข้างเคียงในโลกภายนอกโดยการปรับเปลี่ยนตัวแปรในนั้น โลก.

getId เป็นภาพประกอบที่ไม่บริสุทธิ์

นี่อาจเป็นปัญหาหากเราต้องดีบั๊กโค้ดนี้

มูลค่าปัจจุบันของ id_count คืออะไร? ฟังก์ชั่นอื่นใดที่แก้ไข id_count ? มีฟังก์ชั่นอื่นที่ใช้ id_count หรือไม่

ด้วยเหตุผลเหล่านี้ เราจึงใช้ฟังก์ชันบริสุทธิ์ในการเขียนโปรแกรมเชิงฟังก์ชันเท่านั้น

ประโยชน์อีกประการของฟังก์ชันบริสุทธิ์คือสามารถขนานและบันทึกได้ มาดูสองฟังก์ชั่นก่อนหน้ากัน เป็นไปไม่ได้ที่จะขนานหรือจดจำไว้ ซึ่งช่วยในการสร้างรหัสการปฏิบัติงาน

หลักการของ Functional Programming

จนถึงตอนนี้ เราได้เรียนรู้ว่าการเขียนโปรแกรมเชิงฟังก์ชันนั้นขึ้นอยู่กับกฎสองสามข้อ พวกเขามีดังนี้

  1. อย่าเปลี่ยนข้อมูล
  2. ใช้ฟังก์ชันล้วนๆ: เอาต์พุตคงที่สำหรับอินพุตคงที่ และไม่มีผลข้างเคียง
  3. ใช้นิพจน์และการประกาศ

เมื่อเราปฏิบัติตามเงื่อนไขเหล่านี้ เราสามารถพูดได้ว่าโค้ดของเราใช้งานได้

การเขียนโปรแกรมเชิงฟังก์ชันใน 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 ช่วยให้มีประสบการณ์การเขียนโปรแกรมการทำงานที่ดีขึ้นกว่าเดิมมาก