JavaScript ES6 (ES2015)
Arrow function
// ES6 [1, 2, 3].map(num => num * 2); // ES5 [1, 2, 3].map(function(num) { return num * 2; }); // ES6 app.get('/', (req, res) => { res.json({ message: 'Hello World' }); }); // ES5 app.get('/', function(req, res) { res.json({ message: 'Hello World' }); }); () => ‘Hello’ () => {} (แบบ code block) (num) => num * 2 (num1, num2) => {return num1 + num2;} (แบบ code block) () => ({ foo: ‘bar’ }) (return เป็น Object ให้ใช้ปีกกา)
ลองเล่น
const f = x => x * 2 print(f(2)); //=========================== var materials = [ 'Hydrogen', 'Helium', 'Lithium', 'Beryllium' ]; //จัดให้ดูง่ายสำหรับผม const a = materials.map( function(material) { return material.length; } ); //เปลื่อน function เป็น => ไว้ด้านหลังแทน const b = materials.map( (material) => { return material.length; } ); // ลบ () ของ parameter ออก รวมถึง {},return ของ Function materials.map(material => material.length); //output [8, 6, 7, 9] //==================================== var simple = a => a > 15 ? 15 : a; print(simple(16)); print(simple(10)); // 15 10 const a = (a,b=10) => a*b print(a(2)); // 20 // ต้องใส่ () ด้วย ถ้าจะ Default const a = (a=5) => a print(a());
nested arrow functions
let add = (x,y) => x + y; add(2,3); //=> 5 let add = x => y => x + y; add(2)(3); let add = function (x) { return function (y) { return x + y; }; };
What is Functional Programming
แบบธรรมดา
let numbers = [1, 2, 3, 4, 5, 6 ,7, 8, 9, 10]; let squaredEvens = []; for(let i = 0; i < numbers.length; i++) { let currentNumber = numbers[i]; if(currentNumber % 2 === 0) { squaredEvens.push(currentNumber * currentNumber) } }
แบบ FP
const numbers = [1, 2, 3, 4, 5, 6 ,7, 8, 9, 10]; const isEven = n => n % 2 === 0; const square = n => n * n; numbers.filter(isEven).map(square);
Note
filter รับ parameter เป็น Function ที่โยนไปค่าจาก array ไปเชคทีละตัว
Same
function isEven(n) { return n % 2 === 0; } function square(n) { return n * n; }
หัวใจสำคัญของ Functional Programming
หลีกเลี่ยง side-effect หรือผลข้างเคียงที่จะเกิดต่อ function อื่นและต่อตัวเอง
เมื่อมี input ค่าหนึ่ง จะได้ต้องได้ output เท่าเดิมเสมอ
ไม่ไปเปลี่ยนแปลงค่าของตัวแปรจำพวก global variable หรือ static variable
## First-class function & Higher-order function
First-class function คือ feature ของ programming language ที่อนุญาตให้ function นั้นเป็น first-class citizen ประชากรอันดับหนึ่งของภาษา เทียบเท่ากับ value อื่น ๆ อย่างตัวเลข หรือ string เป็นต้น ซึ่งเราสามารถ assign function ให้กับตัวแปรได้ เป็น argument หรือ return value ของ function อื่นๆก็ได้
function ในลักษณะที่รับ argument เป็น function หรือ return function ออกมามีชื่อเรียกว่า Higher-order function
## Lambda expression & Closure
-
ตัวแปร (ในเชิงคณิตศาสตร์ ซึ่งเป็นตัวแทนของค่า หรือ argument ของ function ไม่ใช่ mutable variable หรือตัวแปรแบบที่เรา update ค่าได้เรื่อยๆเวลาเขียนโปรแกรมแบบ imperative)
- function ซึ่งใน Lambda calculus นั้น function ไม่มีชื่อ
- application ([function][argument]) ถ้าเปรียบเทียบกับพีชคณิตก็คล้ายๆกับแทนค่า parameter ใน function เช่น ƒ(3), g(21)
const foo = [1,2,3]; const baz = foo.map(function bar (n) { return n + 1; });
ส่วน Closure เป็นสิ่งที่ผมสงสัยอยู่นานมาก เพราะมักจะเจอตัวอย่างแบบนี้
function ticker() { let count = 0; return () => count++; } let t = ticker(); t(); // 0 t(); // 1 t(); // 2 t(); // 3
เขาบอกว่า Closures คือ functions ที่อ้างอิง free variables เจ้า free variables เนี่ย มันคือตัวแปรที่ไม่ได้ถูกประกาศไว้ใน parameter จากตัวอย่างด้านบน จะเห็นได้ว่า () => count++ ไม่มี parameter เลย แต่มีตัวแปร count ซึ่งโดยปกติแล้วเมื่อ execute ticker เสร็จ เจ้า count ควรจะถูก garbage collector เก็บไป แต่ในกรณีนี้ เมื่อเราบอกว่า let t = ticker(); แล้ว t จะกลายเป็น closure ซึ่งเป็น function ที่จำสภาพแวดล้อมที่สร้างมันขึ้นมา ทำให้ผลลัพธ์เป็นอย่างที่เห็น แต่ไม่เป็น FP
// Original function makeAdder(a) { return b => a + b; } const addFive = makeAdder(5); const addTen = makeAdder(10); addFive(20); // 5 + 20 => 25 addTen(9); // 10 + 9 => 19 // Refactoring const makeAdder2 = a => b => a + b; const addfuck = makeAdder2(5) addfuck(20)
คราวนี้จะเห็นได้ว่าทั้ง makeAdder, addFive และ addTen ล้วนเป็น pure function หรือตัวอย่างที่ดูฉลาดขึ้นหน่อย
const makeSum = transFunc => (a, b) => transFunc(a) + transFunc(b); const sumSquared = makeSum(n => n * n); const sumCubed = makeSum(n => n * n * n); sumSquared(1, 2); // 1^2 + 2^2 => 5 sumCubed(1, 2); // 1^3 + 2^3 => 9
## รู้จักกับ Javascript Callback Function
### แบบธรรมดา (sync )
console.log('Step 1'); funcSync(); console.log('Step 3'); function funcSync () { console.log('Step 2'); }
### แบบไม่ธรรมดา (async )
setTimeout(function(){ console.log('Step 1') }, 3000); console.log('Step 2') // ผลลัพท์ // Step 2 // Step 1 setTimeout(doSomeThing, 3000); function doSomeThing () { console.log('Hello') }
ฟังชั่น setTimeout จะมีการรับ parameter อยู่ 2 ตัวก็คือ
Function ที่จะให้ทำงานเมื่อถึงเวลาที่กำหนด(หรือก็คือ Callback Function นั่นเอง)
Integer ที่ใช้กำหนดเวลา มีหน่วยเป็น ms
const myAsyncFunc = callback => callback(); myAsyncFunc(callbackFunc); function callbackFunc () { print('this is callback function') }
มันจะทำ function ที่ใส่เข้าไปให้เสร็จก่อนแล้วค่อยออกมา
ลองเล่น
Example 1
// Original asyncFunc( function (str) { print('cb1 : ' + str); }, function (str) { print('cb2 : ' + str); } ); function asyncFunc (cb1, cb2) { cb1('A'); cb2('B'); } // Refactoring let asyncFunc = (cb1,cb2) => { cb1('A'); cb2('B'); } const f1 = str => print('cb1 : ' + str); const f2 = str => print('cb2 : ' + str); asyncFunc(f1,f2);
Example 2
// Original asyncFunc(cb, cb); function asyncFunc (cb1, cb2) { cb1('A'); cb2('B'); } function cb (str) { print('cb : ' + str); } // Refactoring const cb = str => print('cb : ' + str) let asyncFunc = (cb1,cb2) => { cb("A"); cb("B"); } asyncFunc(cb,cb);
Example 3
// Original asyncFunc(cb, cb); function asyncFunc (running, done) { for (var i = 0;i<10;i++) { running('i = ' + i); } done('done'); } function cb (str) { print(str); } // Refactoring let cb = str => print(str); let asyncFunc = (running, done) =>{ for (var i = 0;i<10;i++) { running('i = ' + i); } done('done'); } asyncFunc(cb, cb);
จัดการ Callback
Promise
เป็นเหมือนกับคำสัญญาว่าจะทำคำสั่งนี้ให้เรานะ โดยมันจะมีอยู่ 3 สถานะก็คือ pending, resolved, rejected เมื่อเริ่มต้นทำคำสั่ง promise จะมีสถานะเป็น pending ถ้าทำเสร็จแล้วจะมีสถานะเป็น resolved โดยจะทำคำสั่งถัดไปที่อยู่ใน .then() และถ้าทำคำสั่งไม่สำเร็จ จะมีสถานะเป็น rejected และจะไม่ทำคำสั่งถัดไป แต่จะทำคำสั่งที่อยู่ใน .catch() แทน
var p = new Promise(function(resolve, reject) { let a = 5; if(a >= 5) { resolve(a); }else { reject(a); } }).then(function(a) { console.log(`result then : ${a}`); return a + 2; }).then(function(b) { console.log(`result then : ${b}`); }).catch(function(a) { console.log(`result catch : ${a}`); });
Note
การส่ง return จะไม่ออกมาข้างนอกนะ จะเข้า parameter then ถัดไป
async/await
const resolveAfter2Seconds = x => { return new Promise( resolve => { setTimeout(() => { resolve(x); }, 2000); } ); } console.log(resolveAfter2Seconds(50)); async function add1(x) { const a = await resolveAfter2Seconds(20); const b = await resolveAfter2Seconds(30); return x + a + b; } console.log(add1(100)); add1(10).then(v => { console.log(v); // prints 60 after 4 seconds. }); async function add2(x) { const p_a = resolveAfter2Seconds(20); const p_b = resolveAfter2Seconds(30); return x + await p_a + await p_b; } add2(10).then(v => { console.log(v); // prints 60 after 2 seconds. });
Function Composition
คือกระบวนการรวมกันของ functions มากกว่า 1 ขึ้นไป และก่อให้เกิด function ใหม่ขึ้นมา
หรือรูปแบบหนึ่งที่ใช้ในการอธิบายที่ง่ายที่สุดก็คือ f(g(x)) หรือการรวมกันของ function f และ g มากระทำกับ x
const compose = function(f, g) { return function(x) { return f(g(x)) } } const compose = (f, g) => x => f(g(x))
apply functions ไล่ไปจาก ขวา ไป ซ้าย
ตัวอย่าง
จินตนาการว่า เราจะสร้าง function ทำความสะอาด String ก่อนนำไปใช้งาน (sanitize function)
ขั้นตอนการทำงาน
ตัดช่องว่างที่ไม่จำเป็นหน้าหลังของคำ (trim)
แปลงคำทั้งหมดเป็นตัวพิมพ์เล็ก (trim)
- ตัดช่องว่างที่ไม่จำเป็นหน้าหลังของคำ (trim)
- แปลงคำทั้งหมดเป็นตัวพิมพ์เล็ก (trim)
function sanitize(str) { return str.trim().toLowerCase() } sanitize(' Hello My Name is Ham ') // 'hello my name is ham'
ทีนี้ เราลองเขียนแบบ FP กันดู
const trim = s => s.trim() const toLowerCase = s => s.toLowerCase() function sanitize(str) { return toLowerCase(trim(str)) } sanitize(' Hello My Name is Ham ') // 'hello my name is ham'
const compose = (f, g) => x => f(g(x)) const sanitize = compose(toLowerCase, trim) sanitize(' Hello My Name is Ham ') // 'hello my name is ham'
const trim = s => s.trim() const toLowerCase = s => s.toLowerCase() const join = separator => arr => arr.join(separator) const split = separator => arr => arr.split(separator) const toSlug = compose( toLowerCase, join('-'), split(' '), trim, ) toSlug(' THIS is SluG ') // 'this-is-slug'
Pipe
ลักษณะการทำงานของ pipe คือ การส่งต่อ ผลลัพธ์ ที่ได้จากการ คำสั่งก่อนหน้า ไปให้แก่ คำสั่งด้านหลัง
- compose: apply function จาก ขวา ไป ซ้าย
- pipe: apply function จาก ซ้าย ไป ขวา
const toSlug = pipe( trim, split(' '), join('-'), toLowerCase, ) toSlug(' THIS is SluG ') // 'this-is-slug'
https://github.com/NantipatSoftEn/FunctionalPrograming/blob/master/Higher-Order/README.md
Top comments (2)
^_^ เขียนบันทึกไว้ครับ
ไม่ได้นึกว่าจะมีใครมาอ่าน