WeMo Scooter
Meng
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對我而言...
//
var getServerStuff = function(callback) {
return ajaxCall(function(json) {
return callback(json);
});
};
//
var getServerStuff = ajaxCall;
return ajaxCall(callback);
var getServerStuff = function(callback) {
return ajaxCall(callback);
};
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');
SELECT Id, OrderDate, CustomerId, TotalAmount
FROM [Order]
WHERE NOT (TotalAmount >= 50 AND TotalAmount <= 15000)
ORDER BY TotalAmount DESC
敘述式的程式其實大家都很熟悉...
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
命令式
敘述式
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
命令式
敘述式
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]
var sum = [0, 1, 2, 3].reduce(function(a, b) {
return a + b;
}, 0);
// sum is 6
function isBigEnough(value) {
return value >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
// filtered is [12, 130, 44]
var counter = 0;
function increment() {
return ++counter;
}
避免 side effects
Impure
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
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
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
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隔離
可重複使用
可追蹤
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來自外部,在呼叫時無法直接觀察其變化,從程式碼上較難得知會影響的外部變數
不會改動外部的變數
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
相同的輸入必得到相同的答案
程式碼可以替換成它執行後所得到的結果,而且不改變整個程式行為
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
compose
f • g = f(g(x))
var showStudent = compose(append('#student-info'), csv, find(db));
showStudent('444-44-4444');
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)
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
]
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')))
);
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;
}
}
// 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;
}
Lodash
Ramda
underscore.js