DEV Community

loading...
Cover image for บันทึก FunctionalPrograming

บันทึก FunctionalPrograming

Nantipat
大蛇丸 mode 🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀🎀
・5 min read

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 ให้ใช้ปีกกา)
Enter fullscreen mode Exit fullscreen mode

ลองเล่น

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());
Enter fullscreen mode Exit fullscreen mode

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;
  };
};
Enter fullscreen mode Exit fullscreen mode

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)
  }
}
Enter fullscreen mode Exit fullscreen mode

แบบ 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);
Enter fullscreen mode Exit fullscreen mode

Note

filter รับ parameter เป็น Function ที่โยนไปค่าจาก array ไปเชคทีละตัว

Same

function isEven(n) { return n % 2 === 0; }
function square(n) { return n * n; }
Enter fullscreen mode Exit fullscreen mode

หัวใจสำคัญของ 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; });
Enter fullscreen mode Exit fullscreen mode

ส่วน Closure เป็นสิ่งที่ผมสงสัยอยู่นานมาก เพราะมักจะเจอตัวอย่างแบบนี้

 function ticker() {
   let count = 0;
   return () => count++;
 }
 let t = ticker();
 t(); // 0
 t(); // 1
 t(); // 2
 t(); // 3
Enter fullscreen mode Exit fullscreen mode

เขาบอกว่า 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)
Enter fullscreen mode Exit fullscreen mode

คราวนี้จะเห็นได้ว่าทั้ง 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
Enter fullscreen mode Exit fullscreen mode

## รู้จักกับ Javascript Callback Function
### แบบธรรมดา (sync )

console.log('Step 1');
funcSync();
console.log('Step 3');

function funcSync () {
  console.log('Step 2');
}
Enter fullscreen mode Exit fullscreen mode

### แบบไม่ธรรมดา (async )

 setTimeout(function(){
   console.log('Step 1')
 }, 3000);

 console.log('Step 2')

 // ผลลัพท์
 // Step 2
 // Step 1

setTimeout(doSomeThing, 3000);

function doSomeThing () {
  console.log('Hello')
}
Enter fullscreen mode Exit fullscreen mode

ฟังชั่น setTimeout จะมีการรับ parameter อยู่ 2 ตัวก็คือ
Function ที่จะให้ทำงานเมื่อถึงเวลาที่กำหนด(หรือก็คือ Callback Function นั่นเอง)
Integer ที่ใช้กำหนดเวลา มีหน่วยเป็น ms

 const myAsyncFunc = callback => callback();

myAsyncFunc(callbackFunc);

function callbackFunc () {
  print('this is callback function')
}
Enter fullscreen mode Exit fullscreen mode

มันจะทำ 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);
Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

จัดการ 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}`);
});
Enter fullscreen mode Exit fullscreen mode

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.
});
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

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'
Enter fullscreen mode Exit fullscreen mode

ทีนี้ เราลองเขียนแบบ 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'
Enter fullscreen mode Exit fullscreen mode
const compose = (f, g) => x => f(g(x))
const sanitize = compose(toLowerCase, trim)
sanitize('  Hello My Name is Ham     ') // 'hello my name is ham'
Enter fullscreen mode Exit fullscreen mode
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'
Enter fullscreen mode Exit fullscreen mode

Pipe

ลักษณะการทำงานของ pipe คือ การส่งต่อ ผลลัพธ์ ที่ได้จากการ คำสั่งก่อนหน้า ไปให้แก่ คำสั่งด้านหลัง

  • compose: apply function จาก ขวา ไป ซ้าย
  • pipe: apply function จาก ซ้าย ไป ขวา
const toSlug = pipe(
  trim,
  split(' '),
  join('-'),
  toLowerCase,
)
toSlug('   THIS is SluG    ') // 'this-is-slug'
Enter fullscreen mode Exit fullscreen mode

https://github.com/NantipatSoftEn/FunctionalPrograming/blob/master/Higher-Order/README.md

Discussion (2)

Collapse
Sloan, the sloth mascot
Comment deleted
Collapse
nantipatsoften profile image
Nantipat Author

^_^ เขียนบันทึกไว้ครับ
ไม่ได้นึกว่าจะมีใครมาอ่าน