การเขียนโปรแกรมเชิงฟังก์ชันเป็นกระบวนทัศน์ในการสร้างโปรแกรมคอมพิวเตอร์โดยใช้นิพจน์และฟังก์ชันโดยไม่ทำให้สถานะและข้อมูลกลายพันธุ์
ด้วยการปฏิบัติตามข้อ จำกัด เหล่านี้การเขียนโปรแกรมเชิงฟังก์ชันมีจุดมุ่งหมายเพื่อเขียนโค้ดที่เข้าใจชัดเจนและป้องกันข้อผิดพลาดได้มากขึ้น สิ่งนี้ทำได้โดยหลีกเลี่ยงการใช้คำสั่งควบคุมการไหล (for
, while
, break
, continue
, goto
) ซึ่งทำให้โค้ดติดตามยากขึ้น นอกจากนี้การเขียนโปรแกรมเชิงฟังก์ชันยังกำหนดให้เราต้องเขียนฟังก์ชันที่เป็นตัวกำหนดที่บริสุทธิ์ซึ่งมีโอกาสน้อยที่จะเป็นบั๊กกี้
ในบทความนี้เราจะพูดถึงการเขียนโปรแกรมเชิงฟังก์ชันโดยใช้ JavaScript นอกจากนี้เราจะสำรวจวิธีการและคุณลักษณะของ JavaScript ต่างๆที่ทำให้เป็นไปได้ ในตอนท้ายเราจะสำรวจแนวคิดต่างๆที่เกี่ยวข้องกับการเขียนโปรแกรมเชิงฟังก์ชันและดูว่าเหตุใดจึงมีประสิทธิภาพมาก
ก่อนที่จะเข้าสู่การเขียนโปรแกรมเชิงฟังก์ชันเราจำเป็นต้องเข้าใจความแตกต่างระหว่างฟังก์ชันบริสุทธิ์และไม่บริสุทธิ์
ฟังก์ชั่นบริสุทธิ์รับอินพุตบางส่วนและให้เอาต์พุตคงที่ นอกจากนี้ยังไม่ก่อให้เกิดผลข้างเคียงในโลกภายนอก
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
หรือไม่?
ด้วยเหตุผลเหล่านี้เราจึงใช้เฉพาะฟังก์ชันที่บริสุทธิ์ในการเขียนโปรแกรมเชิงฟังก์ชันเท่านั้น
ประโยชน์อีกประการหนึ่งของฟังก์ชันบริสุทธิ์คือสามารถขนานและบันทึกได้ ดูสองฟังก์ชันก่อนหน้านี้ ไม่สามารถขนานหรือบันทึกได้ สิ่งนี้ช่วยในการสร้างโค้ดประสิทธิภาพ
จนถึงตอนนี้เราได้เรียนรู้แล้วว่าการเขียนโปรแกรมเชิงฟังก์ชันนั้นขึ้นอยู่กับกฎบางประการ มีดังต่อไปนี้
เมื่อเราปฏิบัติตามเงื่อนไขเหล่านี้เราสามารถพูดได้ว่าโค้ดของเราใช้งานได้
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
คัดลอกค่าจากอ็อบเจ็กต์ที่ระบุไปยังอ็อบเจ็กต์ใหม่ เนื่องจากการเขียนโปรแกรมเชิงฟังก์ชันถูกกำหนดไว้ล่วงหน้าบนข้อมูลที่ไม่เปลี่ยนรูปเราจึงใช้มันเพื่อสร้างวัตถุใหม่ตามวัตถุที่มีอยู่
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 เพื่อ refactor ดังต่อไปนี้
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 ช่วยให้มีประสบการณ์การเขียนโปรแกรมเชิงฟังก์ชันที่ดีขึ้นกว่าเดิมมาก
การเขียนโปรแกรมเชิงฟังก์ชันเป็นกระบวนทัศน์ของการสร้างโปรแกรมคอมพิวเตอร์โดยใช้การประกาศและนิพจน์
ด้วยการพัฒนาใหม่ ๆ ใน ES6 เราสามารถพูดได้ว่า JavaScript เป็นทั้งภาษาการเขียนโปรแกรมเชิงฟังก์ชันและเชิงวัตถุเนื่องจากคุณสมบัติชั้นหนึ่งต่างๆที่มีให้
การเขียนโปรแกรมเชิงฟังก์ชันช่วยให้สามารถควบคุมการไหลในโค้ดของเราได้ง่ายขึ้นและหลีกเลี่ยงความประหลาดใจใด ๆ ในรูปแบบของตัวแปรและการเปลี่ยนแปลงสถานะ ทั้งหมดนี้ช่วยให้เราหลีกเลี่ยงจุดบกพร่องและเข้าใจโค้ดของเราได้ง่าย
Lisp, Erlang, Haskell, Closure และ Python เป็นภาษาโปรแกรมที่ใช้งานได้อื่น ๆ ในสิ่งเหล่านี้ Haskell เป็นภาษาการเขียนโปรแกรมที่ใช้งานได้อย่างแท้จริงในแง่ที่ว่ามันไม่อนุญาตให้มีกระบวนทัศน์อื่น ๆ ในการเขียนโปรแกรม
ES6 หรือ ECMAScript 6 เป็น JavaScript เวอร์ชันใหม่ที่มีคุณสมบัติใหม่ ๆ มากมายเช่นฟังก์ชันลูกศรค่าคงที่และตัวดำเนินการกระจายและอื่น ๆ อีกมากมาย