JavaScript Functional Programing

WeMo Scooter

Meng

Why?

  • 你可能每天都在工作中使用到它。
    • functional是一種觀念,有機會可以解決工作上遇到的問題
  • 我們不必從頭學習所有東西就能開始撰寫程式。​
    • 想用就用,也不必一口氣完全導入
  • JS完全有能力撰寫高級的 functional 程式碼。
    • ​借助一些第三方的library就可以實現functional programming的各種特性

導讀項目

  • Think functionally
    • Becoming functional
    • Higher-order JavaScript
  • Get functional
    • Few data structures, many operations

    • Toward modular, reusable code

    • Design patterns against complexity

  • Enhancing your functional skills

OO makes code understandable by encapsulating moving parts.

 

FP makes code understandable by minimizing moving parts.

 

Michael Feathers (Twitter)

Functional programming isn’t a framework or a tool, but a way of writing code, thinking functionally is radically different from thinking in object-oriented terms.

1.更接近人類的語言

2.更加模組化

3.更容易測試

Functional對我而言...

背景知識

  • First Class Funtcion​
    • function可以當作變數傳入function當中
// 
var getServerStuff = function(callback) {
  return ajaxCall(function(json) {
    return callback(json);
  });
};


// 
var getServerStuff = ajaxCall;
return ajaxCall(callback);
var getServerStuff = function(callback) {
  return ajaxCall(callback);
};

背景知識

  • Curry
    • 只透過部分的參數呼叫一個 function,它會回傳一個 function 去處理剩下的參數。
let add = x => y => x + y;


var increment = add(1);

increment(1) // -> 2
var add = function(x) {
  return function(y) {
    return x + y;
  };
};

var increment = add(1);

increment(1) // -> 2

簡單的例子

document.querySelector('#msg').innerHTML = '<h1>Hello World</h1>';
function printMessage(elementId, format, message) {
    document.querySelector(`#${elementId}`).innerHTML =
         `<${format}>${message}</${format}>`;
}

printMessage('msg', 'h1','Hello World');
var printMessage = run(addToDom('msg'), h1, echo);

printMessage('Hello World');

pointfree

簡單的例子

將原本的程式修改

將msg 使用h2標籤 並且 重複 print 在console上面3次

var printMessage = run(addToDom('msg'), h1, echo);

printMessage('Hello World');
var printMessage = run(console.log, repeat(3), h2, echo);

printMessage('Get Functional');

Functional基本概念

  1. Declarative programming
  2. Pure functions
  3. Referential transparency
  4. Immutability

Declarative programming

SELECT Id, OrderDate, CustomerId, TotalAmount
FROM [Order]
WHERE NOT (TotalAmount >= 50 AND TotalAmount <= 15000)
ORDER BY TotalAmount DESC

敘述式的程式其實大家都很熟悉...

Declarative programming

var name = "peter";
var greeting = "Hi, I'm "
console.log(greeting + name);
function greet(name) {
    return "Hi, I'm " + name;
}

greet("Peter");

// => Hi, I'm Peter

命令式

敘述式

Declarative programming

var array = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
for(let i = 0; i < array.length; i++) {
   array[i] = Math.pow(array[i], 2);
}

array; //-> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => Math.pow(num, 2))

array; //-> [0, 1, 4, 9, 16, 25, 36, 49, 64, 81

命令式

敘述式

Declarative programming

var numbers = [1, 5, 10, 15];
var roots = numbers.map(function(x){
   return x * 2;
});
// roots is now [2, 10, 20, 30]
// numbers is still [1, 5, 10, 15]

Declarative programming

var sum = [0, 1, 2, 3].reduce(function(a, b) {
  return a + b;
}, 0);
// sum is 6

Declarative programming

function isBigEnough(value) {
  return value >= 10;
}

var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
// filtered is [12, 130, 44]

Pure functions

var counter = 0;
function increment() {
   return ++counter;
}

避免 side effects

Impure

Pure functions

var xs = [1, 2, 3, 4, 5];

// pure(純)
xs.slice(0, 3);
//=> [1, 2, 3]

xs.slice(0, 3);
//=> [1, 2, 3]

xs.slice(0, 3);
//=> [1, 2, 3]


// impure(不純)
xs.splice(0, 3);
//=> [1, 2, 3]

xs.splice(0, 3);
//=> [4, 5]

xs.splice(0, 3);
//=> []

避免 side effects

Pure functions

function showStudent(ssn) {
    var student = db.get(ssn);
    if(student != null) {
        document.querySelector(`#$(elementId)`).innerHTML = 
        `${student.ssn},
         ${student.firstname},
         ${student.lastname}`;    
    }
    else {
        throw new Error('Student not found!');        
    }
}

showStudent('444-44-4444');·

使用外部函式

從DB取得資料,假設該函式為同步函式

若找不到資料則拋出例外

找出學生資料並顯示在畫面當中

避免 side effects

Pure functions

function showStudent(ssn) {
    var student = db.get(ssn);
    if(student != null) {
        document.querySelector(`#$(elementId)`).innerHTML = 
        `${student.ssn},
         ${student.firstname},
         ${student.lastname}`;    
    }
    else {
        throw new Error('Student not found!');        
    }
}

showStudent('444-44-4444');·

會讓程式中止

db來自外部,有可能不存在。

相同參數每次得到的結果可能不同

來自外部的變數
無法確定內容

直接改變了HTML的內容

problem with side effects

Pure functions

problem with side effects

var find = curry((db, id) => {
    var obj = db.get(id);
    if(obj === null) {
        throw new Error('Object not found!');    
    }
    return obj;
});

var csv = (student) => {
    return `${student.ssn}, ${student.firstname}, ${student.lastname}`;
};

var append = curry((elementId, into) => {
    document.querrySelector(elementId).innerHTML = info;
})


var showStudent = run(
    append('#student-info'),
    csv,
    find(db)
)

showStudent('444-44-4444')

更易讀

pure/ impure function隔離

可重複使用

可追蹤

Referential transparency

var counter = 0;

function increment() {
    return ++counter;
}

increment();
increment();
...
print(counter);  //-> ?
var increment = counter => counter + 1;

var plus2 = run(increment, increment);
print(plus2(0));

counter來自外部,在呼叫時無法直接觀察其變化,從程式碼上較難得知會影響的外部變數

不會改動外部的變數

Referential transparency

var input = [80, 90, 100];
var average = (arr) => divide(sum(arr), size(arr));
average (input); //-> 90
var input = [80, 90, 100];
var average = divide(270, 3); //-> 90
average (input); //-> 90

相同的輸入必得到相同的答案

程式碼可以替換成它執行後所得到的結果,而且不改變整個程式行為

 immutable

var sortDesc = function (arr) {
  return arr.sort(function (a, b) {
     return b - a;
  });
}

var arr = [1,2,3,4,5,6,7,8,9];
sortDesc(arr); //-> [9,8,7,6,5,4,3,2,1]

arr; //-> [9,8,7,6,5,4,3,2,1]

資料建立後就不能更改,目的是避免side-effect

Functional的基本模式

compose

f • g = f(g(x))

var showStudent = compose(append('#student-info'), csv, find(db));
showStudent('444-44-4444');

Functional的基本模式

compose

f • g = f(g(x))

var compose = function(f, g) {
  return function(x) {
    return f(g(x));
  };
};
// 結合律(associativity)
var associative = compose(f, compose(g, h)) == compose(compose(f, g), h);
// true

Processing data using fluent chains

let enrollment = [
  {enrolled: 2, grade: 100},
  {enrolled: 2, grade: 80},
  {enrolled: 1, grade: 89}
];
var totalGrades = 0;
var totalStudentsFound = 0;
for(let i = 0; i < enrollment.length; i++) {
    let student = enrollment [i];
    if(student !== null) {
       if(student.enrolled > 1) {
          totalGrades+= student.grade;
          totalStudentsFound++;
       }
    }
 }
 var average = totalGrades / totalStudentsFound; 
 //-> 90
_.chain(enrollment)
 .filter(student => student.enrolled > 1)
 .pluck('grade')
 .average()
 .value(); // -> 90


// (Underscore.js)

Functional的基本模式

Functional的基本模式

const map = fn => array = > array.map(fn);
const multiply = x => y => x * y;
const pluck = key => object => object[key];

const discount = multiply(0.98);
const tax = multiply(1.0925);

const customRequest = request({
    header:{ 'X-Custom': 'myKey'
});

customRequest({url: '/cart/items' })
    .then(map(pluck('price')))
    .then(map(discount))
    .then(map(tax));

Promise chains

[
    {price: 5},
    {price: 10},
    {price: 3}
]
[
    5,
    10,
    3
]
[
    5 * 0.98,
    10 * 0.98,
    3 * 0.98
]

Functional的基本模式

const map = fn => array = > array.map(fn);
const multiply = x => y => x * y;
const pluck = key => object => object[key];

const discount = multiply(0.98);
const tax = multiply(1.0925);

const customRequest = request({
    header:{ 'X-Custom': 'myKey'
});

customRequest({url: '/cart/items' })
    .then(map(pluck('price')))
    .then(map(discount))
    .then(map(tax));

Promise chains

customRequest({url: '/cart/items' })
    .then(
        map(compose(tax, discount, pluck('price')))
    );

比較functional與OO

OO遇到的問題

class Person {
    constructor (firstname, lastname ,ssn){
        this._firstname = firstname;
        this._lastname = lastname;
        this._ssn = ssn;
        this.address = null;
    }

    get address() {
        return this._address;   
    }
    
    set address(addr) {
        this._address = addr;
    }
    .....
}
class Student extends Persion {
    constructor (firstname, lastname, ssn, school){
        super(firstname, lastname ,ssn);
        this._school = school
    }

    get school() {
        return this._school;   
    }
    
}

OO遇到的問題

// Person class
peopleInSameCountry(friends) {
    var result = [];
    for(let idx in friends) {
        var friend = friends[idx];
        if(this.address.country === friend.address.country){
            result.push(friend);
        }
    }
    return result;
};


// Student class
peopleInSameCountryAndSchool(friends) {
    var closeFriends = super.peopleInSameCountry(friends);
    var result = [];
    for (let idx in closeFriends) {
        var friend = closeFriends[idx];
        if(this.school === friend.school){
            result.push(friend)
        }
    }
    return result;
}

OO遇到的問題

Functional Library

Lodash

Ramda

underscore.js

Functional Programing in ES6

By MengWei Chen

Functional Programing in ES6

  • 1,039