前提:為了培養團隊的技術能力,拿了個小修改需求作為函數式程式設計的實踐機會。
需求:業務部門提出積分活動,當使用者達成任務時送積分。簡化邏輯,我們暫且認為逢5送5分,逢10送10分,其他送1分。
即達成任務1-4次時,每次送1分。第5次送5分,第10次送10分。
實作過程:
mkdir [項目路勁]
npm init --y
npm install typescript ts-node --save-dev
//測試用
npm install jasmine @types/jasmine --save-dev
建立檔案fpdemo.ts
為了實作函數式,先定義兩個幫助方法:
1)等價于if/else的either
2)等價于for循環的repeat
either的目的是傳入一個條件、左分支、右分支,根據條件決定執行左分支還是右分支。右分支還能嵌套執行either方法。
repeat代替for循環,目的是依次執行每個either方法。
全部代碼實作如下
export namespace fpdemo {
//獎勵積分常量
const FifthAward = 5;
const TenthAward = 10;
const OthersAward = 1;
//第五次
export const IsFiveTimes = (arg: number)=> arg % 5 === 0;
//第十次
export const IsTenTimes = (arg: number)=> arg % 10 === 0;
type eitherFn = <U, T>(value: U) => () => T;
//這裡使用函數重載,right可以是一個數值,也可以嵌套的either方法
function either<U>(condition: (arg: U) => boolean, left: number, right: number): any
function either<U>(condition: (arg: U) => boolean, left: number, right: eitherFn): any
function either<U>(condition: (arg: U) => boolean, left: number, right: number|Function): any
{
return (value: U) => condition(value)? left :
typeof right === "number"? right : right(value);
}
//代替for循環
function repeat(current: number, cap: number, fn: Function, total=0): any {
total += fn(current);
return current>=cap? total : repeat(++current, cap, fn, total);
}
console.log(
//傳入數值,判斷獎勵的數量
either(IsTenTimes, TenthAward, either(IsFiveTimes, FifthAward, OthersAward))(10)
);
//從1-10,累加獎勵
console.log(repeat(1, 10, either(IsTenTimes, TenthAward, either(IsFiveTimes, FifthAward, OthersAward))));
}
ts-node fpdemo.ts
執行結果可以看到1-10次的累加總額。
說到這裡,可能有人會有疑問,費了這麼大的勁,就為了寫個for+if/else就能解決的問題。 傳統(junior)的寫法,會這麼做
let times = 10;
let totalAward = 0
for(let i=0; i<times; i++ )
{
if(i%10 == 0)
{
totalAward += 10;
}
else if(i%5 == 0)
{
totalAward += 5;
}
else {
totalAward += 1;
}
}
依我的了解,fp的好處至少有兩點
1:可複用 - 代碼裡的每個function都可以單獨導出,并在他處複用
2:可測試 - 由于沒有外部依賴,函數都是幂等的,是以每個function都可以導出并單元測試
最後安裝jasmine, 進行單元測試
添加配置檔案jasmine.json
{
"spec_files": ["**/*[sS]pec.ts"]
}
import * as demo from './fpdemo';
describe('five', function() {
it('add', function() {
let result = demo.fpdemo.IsFiveTimes(5);
expect(result).toEqual(true);
})
})