ทำไมต้องเป็น Functional Programming?
เริ่มต้นด้วยการยกตัวอย่าง:
const printOddNumbers = (ar) => { let odd = []; for(let i=0;i<ar.length;i++) { if(ar[i]%2==1) { odd.push(ar[i]); } } for(let i=0;i<odd.length;i++) { console.log(odd[i]); } } printOddNumbers([1,2,3,4,5,6]);
const logger = e => console.log(e); const printOddNumbers = (ar) => { ar.filter(x => x%2) .forEach(logger); }; printOddNumbers([1,2,3,4,5]);
เรามี 2 โปรแกรมสำหรับพิมพ์เลขคี่ไปที่คอนโซล ความแตกต่างระหว่างกระบวนทัศน์การเขียนโปรแกรมเหล่านี้คือลักษณะที่เราอ่านและเขียนโค้ด
Imperative :
Imperative code หมายถึงโค้ดที่เน้นไปที่HOWเป็นหลักในการทำบางสิ่งในแง่ของคำสั่ง/ขั้นตอน เนื่องจากลักษณะHOWของสไตล์นี้ สมองของเราต้องรันโค้ดทางจิตใจก่อนที่เราจะเข้าใจจุดประสงค์ของมัน เราไม่สามารถเหลือบมองโค้ดและเข้าใจได้ทันทีว่าโค้ดนั้นกำลังทำอะไรอยู่
Declarative :
- ในรูปแบบของ declarative How จะไม่สำคัญ สิ่งที่สำคัญคือWhat ในรูปแบบนี้ระบุโปรแกรมWhatที่ต้องทำในแง่ของการแสดงออก Functional Programming นั้นโดยธรรมชาติแล้วจะมีความชัดเจนมากกว่า
หากเราเขียนโค้ดที่บังคับให้บุคคลนั้นใช้จิตใจในการรันโค้ดในหัวเพียงเพื่อที่พวกเขาจะได้เข้าใจ มันคือโค้ดที่เข้าใจยากกว่า ดูแลรักษา ปรับปรุง หรือแก้ไข ดังนั้น เป้าหมายคือเปลี่ยนจุดสนใจของผู้อ่านโค้ดของเรา ไปสู่ลักษณะการประกาศให้ห่างจากธรรมชาติที่จำเป็น เพื่อให้เข้าใจง่าย
Functional Programming คืออะไร?
ในกระบวนทัศน์การเขียนโปรแกรมเชิงฟังก์ชัน เราสร้างโปรแกรมของเราโดยใช้ฟังก์ชันบริสุทธิ์เท่านั้น เพื่อให้ฟังก์ชันบริสุทธิ์ ควรมีความโปร่งใสในการอ้างอิง ฉันจะอธิบายว่าการอ้างอิงโปร่งใสหมายถึงอะไรในบางครั้ง แต่ก่อนอื่น เราจะเข้าใจว่าคำจำกัดความที่แท้จริงของฟังก์ชันในจิตวิญญาณของการเขียนโปรแกรมเชิงฟังก์ชันคืออะไร
ฟังก์ชั่นบริสุทธิ์คืออะไร?
ในจิตวิญญาณของการเขียนโปรแกรมเชิงฟังก์ชัน คำจำกัดความที่เหมาะสมของฟังก์ชันบริสุทธิ์คือ
- ฟังก์ชันไม่เพียงรับอินพุตบางส่วนเท่านั้น แต่ยังต้องส่งคืนเอาต์พุตบางส่วนด้วย ตัวอย่างด้านล่างไม่เข้าเกณฑ์สำหรับฟังก์ชันเนื่องจากไม่ส่งคืนผลลัพธ์ใดๆ แทน ทำให้เกิดผลข้างเคียงโดยการพิมพ์ไปยังคอนโซล
const areaOfCircle = (radius) => { console.log(3.14 * radius * radius); }; areaOfCircle(2);
- ฟังก์ชั่นจะดีกว่าถ้าชื่อฟังก์ชันสามารถอธิบายความสัมพันธ์ทางความหมาย b/w อินพุตและเอาต์พุต
f(r) = π * r * rในตัวอย่างข้างต้น areaOfCircle อธิบายความสัมพันธ์เชิงความหมาย b/w ฟังก์ชันอินพุตและเอาต์พุตได้อย่างสมบูรณ์แบบ
- ในฟังก์ชัน อินพุตและเอาต์พุตควรเป็นแบบตรง การเรียกใช้ฟังก์ชันควรทำธุรกิจแยกจากส่วนที่เหลือของโปรแกรม มันไม่ควรทำให้เกิดใด ๆ ที่มีผลข้างเคียง ในตัวอย่างด้านล่าง ทั้งอินพุตและเอาต์พุตเป็นทางอ้อมและได้รับการเปลี่ยนแปลงตลอดการดำเนินการของโปรแกรม
let pi = 3.14; let radius, area; const areaOfCircle = () => { radius = pi * radius * radius; return radius; }; radius = 2; area = areaOfCircle(pi, radius) console.log(area); radius = 3; area = areaOfCircle(pi, radius) console.log(area);
- ฟังก์ชันสามารถมีอินพุตทางอ้อมได้ตราบเท่าที่ไม่มีการกลายพันธุ์หรือเปลี่ยนแปลง อินพุตทางอ้อมควรคงที่ตลอดหลักสูตร ที่นี่ pi ไม่เคยเปลี่ยนแปลง ดังนั้นตัวแปร pi เนื่องจากอินพุตทางอ้อมจึงถูกต้อง
const pi = 3.14; const areaOfCircle = (radius) => { // pi is in-direct input here, // it is as equivalent as replacing the value of pi // inline i.e 3.14 * radius * radius return pi * radius * radius; }; let area1 = areaOfCircle(2); console.log(area1); let area2 = areaOfCircle(3); console.log(area2);
- การเรียกใช้ฟังก์ชันควรส่งคืนเอาต์พุตเดียวกันผ่านอินพุตเดียวกันกี่ครั้งก็ตามเมื่อมีการเรียก
function counter() { let c = 0; // we are closing over the value c, // forming a closure and mutating it on every function execution // resulting in different output. return function() { return ++c; } } const myCounter = counter(); // these are not pure function call, as we are not getting // the same output every time, given the same input which in this // case is no input. myCounter() // 1 myCounter() // 2 myCounter() // 3 // Same with random number generator function random() { return Math.random(); } // no input, is the valid input random(); random(); random();
ทั้งการเรียกใช้ฟังก์ชัน areaOfCircle และ areaOfCylinder มีความโปร่งใสในการอ้างอิง เนื่องจากเราสามารถแทนที่การเรียกใช้ฟังก์ชันด้วยค่าที่คำนวณได้อย่างแท้จริง โดยไม่กระทบกับส่วนที่เหลือของโปรแกรม มันเหมือนกับการแก้ไขนิพจน์ภายในและแทนที่ผลลัพธ์เพื่อแก้นิพจน์ที่ใหญ่ขึ้น เป็นต้น บน.
เนื่องจากความโปร่งใสในการอ้างอิงมากเกินไป เราจึงรับประกันผลลัพธ์ของการเรียกใช้ฟังก์ชัน ดังนั้นคอมไพเลอร์ภาษาสามารถใช้ประโยชน์จากมันและจดจำผลลัพธ์ของการเรียกใช้ฟังก์ชันสำหรับอินพุตที่กำหนด และแทนที่ค่าของมันทุกที่อย่างแท้จริง เพื่อไม่ให้คำนวณค่าเดียวกันอีกและ อีกครั้ง.
สิ่งนี้ยังให้ประโยชน์กับผู้อ่านโค้ด และหากเราสามารถทำการแทนที่แบบเดียวกันได้ เพื่อที่จะเข้าใจและรับผลลัพธ์และแก้ไขนิพจน์ได้อย่างรวดเร็ว
const pi = 3.14; function areaOfCircle(radius) { return pi * radius * radius; } function areaOfCylinder(radius, height) { return areaOfCircle(radius) * height; // return areaOfCircle(2) * height is // equivalent to // return 12.56 * height; // We just replace it's result as we are guranteed by the // fact that areaOfCircle is a pure function and always // result in same output on input } let cylinderArea = areaOfCylinder(2,3); console.log(cylinderArea);
คุณอาจจะคิดนี้อาจทำให้ CPU และค่าหน่วยความจำที่เราจะได้รับการคัดลอกข้อมูลเดียวกันซ้ำแล้วซ้ำอีกครั้งนั่นคือสิ่งที่เราได้รับความช่วยเหลือจากห้องสมุดไม่เปลี่ยนรูปโครงสร้างข้อมูลเช่นImmutable.js และ Google ฝรั่ง พวกเขาสรุปสิ่งเหล่านี้ออกจากบริบทตรรกะทางธุรกิจของเราและดูแลเรื่องนี้อย่างมีประสิทธิภาพมากขึ้น ข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่พวกเขาทำในบล็อกอื่น
เป็นไปไม่ได้ที่จะมีโปรแกรมที่ใช้งานได้จริงโดยไม่มีผลข้างเคียง ดังนั้น คำถามคืออย่าให้ 0 ผลข้างเคียง แต่เป็นการลดผลข้างเคียงให้น้อยที่สุด การลดผลข้างเคียงจะทำให้เราดีบักได้เร็วขึ้น และจะช่วยให้เราค้นหาจุดบกพร่องได้จากจุดใด เนื่องจากบั๊กมีแนวโน้มที่จะเกิดขึ้นเมื่อมีผลข้างเคียงและการกลายพันธุ์ของสถานะเกิดขึ้น
หวังว่าบล็อกนี้จะช่วยคุณในการทำความเข้าใจเหตุผลเบื้องหลังการเขียนโปรแกรมเชิงฟังก์ชัน การเขียนโปรแกรมเชิงฟังก์ชันยังมีอะไรอีกมากมาย เช่น การใช้งานบางส่วน การแกง องค์ประกอบของฟังก์ชัน เพิ่มเติมเกี่ยวกับเรื่องนี้ในภายหลัง แล้วเจอกัน!!.
Credit : Medium.com