函數式編程的思惟方式是把現實世界的事物和事物之間的聯繫抽象到程序世界(對運算過程進行抽象). (本篇文章內容輸出來源:《拉鉤教育大前端訓練營》部分參考書籍:《JavaScript忍者祕籍》《你不知道的JavaScript 卷一》關於函數部分的講解 進行總結)javascript
本章重點掌握Javascript中的高階函數知識以及函數式編程.html
爲何要學習函數式編程?前端
什麼是函數式編程(Functional Programming, FP):FP 是編程範式之一.(還有面向過程編程、面向對象編程)vue
面向對象編程的思惟方式: 把現實世界中的事物抽象成程序世界中的類和對象,經過封裝、繼承和多態來演示事物事件的聯繫java
函數式編程的思惟方式是把現實世界的事物和事物之間的聯繫抽象到程序世界(對運算過程進行抽象).node
y=sin(x)
,x和y的關係function test(x){
return x * x;
}
複製代碼
在Javascript中函數是一等公民,函數能夠存儲在變量中、函數做爲參數、函數能夠做爲返回值.react
高階函數程序員
函數做爲參數,以下代碼實現的是循環遍歷數組,經過傳遞參數回調函數能夠拿到每一個數組遍歷的值在回調函數中進行相應的處理web
//模擬forEach
function forEach(array, fn) {
for (let index = 0; index < array.length; index++) {
const element = array[index];
fn(element);
}
}
複製代碼
函數做爲返回值,以下函數能夠做爲返回值,以下代碼通常來講函數做爲返回值是閉包的表現,關於閉包的概念會在後面詳細的學習數據庫
function test(x){
return function(y){
return x + y;
}
}
let a = test(1)(2);//3
複製代碼
高階函數的意義
面向過程方式與函數式編程方式對比
經常使用高階函數,下面來模擬JavaScript中的自帶的高階函數,以下代碼經常使用的高階函數大量都使用了以函數做爲參數,進行回調。只須要拿到結果進行處理便可。
//模擬forEach
function forEach(array, fn) {
for (let index = 0; index < array.length; index++) {
const element = array[index];
fn(element);
}
}
複製代碼
//模擬filter
function filter(array, fn) {
let result = [];
for (let index = 0; index < array.length; index++) {
const element = array[index];
if (fn(element)) {
result.push(element);
}
}
return result;
}
複製代碼
//every 數組的全部元素進行某種操做所有爲真匹配條件才返回真 不然只要有一個不成立就會返回false假
const every = (arr, fn) => {
let result = false;
for (const iterator of arr) {
result = fn(iterator);
//只要有一個返回爲false就不成立
if (!result) {
break;
}
}
return result;
}
複製代碼
//模擬some函數 數組中的元素只要有一個元素匹配條件返回爲true,只有全部元素所有不匹配條件纔會返回false
const some = (arr, fn) => {
let result = false;
for (const value of arr) {
result = fn(value);
if (result) {
break;
}
}
return result;
}
複製代碼
//模擬once函數 只能執行一次
function once(fn) {
let done = false;
return function () {
if (!done) {
done = true;
return fn.apply(this, arguments);//調用function() 傳遞的參數 傳遞到fn
}
}
}
let pay = once((money) => {
console.log(`支付了${money} RMB`);
});
複製代碼
//模擬map函數 對數組中對每個元素遍歷改變每個元素的值 使用const 不但願函數被修改定義爲常量
const map = (array, fn) => {
let results = [];
for (const value of array) {
results.push(fn(value));//獲得的是fn的處理的結果
}
return results;
}
複製代碼
閉包:函數和其周圍的狀態(詞法環境)的引用捆綁在一塊兒造成閉包.
如上述的once
函數,返回的新的函數依然能夠調用once()
函數中的內部變量done
function once(fn) {
let done = false;
return function () {
if (!done) {
done = true;
return fn.apply(this, arguments);//調用function() 傳遞的參數 傳遞到fn
}
}
}
複製代碼
閉包的深刻理解
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script> /* 閉包的案例 */ Math.pow(4,2);//4的二次方 5的二次方 //經過一個函數來簡化求平方 function makePow(power){ //返回一個函數求傳遞的數的power次冪 return function(value){ return Math.pow(value,power); } } //求平方 let power2 = makePow(2); //求三次方 let power3 = makePow(3); console.log(power2(2)); console.log(power2(4)); console.log(power3(4)); </script>
</body>
</html>
複製代碼
下面咱們經過調試上述的代碼,來看一下閉包的過程
以下圖,重點關注的有兩個地方,一個設置調試點而後刷新頁面能夠看到右側的調試工具,重點關注右側的Call Stack
(調用棧)以及Scope
(做用域)能夠看到目前所處的做用域在Global
全局做用域中.
按F11或command + ;
執行下一步以下結果此時執行makePow
函數,能夠看到調用棧Call Stack
的棧頂爲makePow
,而Scope
做用域多了一個Local
就是局部做用域裏面存儲着power
和this:Window
經過調試咱們能夠看到不少有用的信息,幫助咱們去理解程序.
而後咱們讓程序執行到log的步驟執行的狀況,看下面的視圖,能夠看到Scope
中有一個Script
的做用域存儲着let
變量的值,也就是let
有一個單獨的做用域Script
.
後面的重點來了,而後咱們繼續往下執行一步,以下視圖能夠看到調用棧會執行power2()
匿名函數,那麼這個匿名函數中power
是從哪裏來的呢?看Scope
部分多了一個Closure(makePow)
它就是一個閉包
,引用了makePow
的power:2
. 上述中講到的當閉包發生後外部函數會從調用棧移除掉,可是與閉包相關的變量會被緩存下來,這個例子緩存下來的就是power
.
在看一下執行power3
的狀況,一樣緩存下來power:3
.這樣就是閉包的一個完整的過程.經過調試這樣就能夠很清晰的瞭解閉包的概念以及實現的過程比理解純理論上的東西要容易的多,因此所學習更多的是要掌握方法.
純函數:相同的輸入永遠會獲得相同的輸出,並且沒有任何可觀察的反作用
let array = [1,2,3,4,5];
console.log(array.slice(0,3));
console.log(array.slice(0,3));
console.log(array.slice(0,3));
//輸入相同 輸出也相同就是一個純函數
//[ 1, 2, 3 ]
// [ 1, 2, 3 ]
// [ 1, 2, 3 ]
//splice 就不是一個純函數 由於輸入相同可是每次的輸出結果不一樣
console.log(array.splice(0,3));
console.log(array.splice(0,3));
console.log(array.splice(0,3));
//splice 相同的輸入 每次輸出的結果不相同 那麼就是一個不純的函數
//[ 1, 2, 3 ]
//[ 4, 5 ]
//[]
//寫一個純函數
function getSum(n1,n2){
return n1 + n2;
}
console.log(getSum(1,2));
console.log(getSum(1,2));
console.log(getSum(1,2));
// 3
// 3
// 3
複製代碼
lodash庫的使用,須要在nodejs的環境下引入lodash庫
//first last toUpper reverse each includes find findIndex
const _=require('lodash');
const array = ['jake','tom','lucy','kate'];
console.log(_.first(array));//jake 純函數
console.log(_.last(array));//kate 純函數
console.log(_.toUpper(_.first(array)));//JAKE 純函數
console.log(_.reverse(array));//[ 'kate', 'lucy', 'tom', 'jake' ] 注意:內部調用的是數組的reverse 而數組的reverse 會改變原有數組不是一個純函數的方法
const r = _.each(array,(item,index)=>{
console.log(item,index);
});
console.log(r);
const l = _.find(array,(item)=>{
return item === 'jake';
});
console.log(l,array);
複製代碼
lodash的memoize函數
const _ = require('lodash');
function getArea(r) {
console.log(r);
//計算圓的面積
return Math.PI * r * r;
}
//lodash的memoize方法 接收一個純函數 對純函數的結果緩存 返回一個帶有記憶功能的函數
// let getAreaWithMemory = _.memoize(getArea);
// console.log(getAreaWithMemory(4));
// console.log(getAreaWithMemory(4));
// console.log(getAreaWithMemory(4));
/* 4 表示getArea這個函數只執行了一次 50.26548245743669 50.26548245743669 50.26548245743669 */
複製代碼
手動實現memoize函數
//模擬memoize方法的實現
function memoize(fn){
let cache = {};
return function(){
//1 判斷cache是否有這個fn的結果
let key = JSON.stringify(arguments);//將傳遞的參數做爲key
cache[key] = cache[key] || fn.apply(fn,arguments);//若是沒有值調用fn() 結果做爲值
return cache[key];
}
}
let getAreaWithMemory = memoize(getArea);
console.log(getAreaWithMemory(4));
console.log(getAreaWithMemory(4));
console.log(getAreaWithMemory(4));
/* 結果以下: 4 50.26548245743669 50.26548245743669 50.26548245743669 */
複製代碼
//不純的函數 一旦mini的值發生了改變就會是函數變的不純 正是對外部的依賴致使的反作用
let mini = 18;
function checkAge(age){
return age >= mini;
}
//純的 (硬編碼 後續會經過柯里化解決)
function makeCheckAge(age){
let mini = 18;
return age >= mini;
}
複製代碼
反作用讓一個函數變的不純,純函數的根據相同的輸入返回相同的輸出,若是函數依賴於外部的狀態就沒法保證輸出相同,就會帶來反作用.
反作用的來源
全部的外部交互都有可能代來反作用,反作用也使得方法通用性降低不適合擴展和可重用性;同時反作用會給程序中帶來安全隱患給程序帶來不肯定性,可是反作用不可能徹底禁止,儘量控制它們在可控範圍內發生.
使用柯里化解決純函數的反作用.什麼是柯里化呢? 當函數有多個參數的時候,對函數進行改造調用一個函數只傳遞並返回一個新的函數(這部分參數之後永遠不會發生變化),這個新的函數去接收剩餘的參數,返回結果。
//硬編碼
function checkAge(age){
let min = 18;
return age >= min;
}
//解決硬編碼的問題 普通的純函數
function checkAge(min,age){
return age >= min;
}
console.log(checkAge(18,20));//true
//解決基準值的問題 經過閉包的方式
function checkAge(min) {
return function (age) {
return age >= min;
}
}
let checkAge = min => ((age) =>(age>=min));
let checkAge18 = checkAge(18);
let checkAge20 = checkAge(20);
console.log(checkAge18(20));
console.log(checkAge18(24));
console.log(checkAge20(20));
console.log(checkAge20(24));
複製代碼
lodash 通用的柯里化方法
curry(func) 建立一個函數而且該函數接收一個或多個func的參數,若是func所須要的參數,若是func所須要的參數都被提供則
則執行func並返回執行的結果,不然繼續返回該函數並等待接受剩餘的參數
參數:須要柯里化的函數
返回值:柯里化後的函數
const _ = require('lodash');
function getSum(a, b, c) {
return a + b + c;
}
const curried = _.curry(getSum);
console.log(curried(1,2,3));
console.log(curried(1,2)(3));
console.log(curried(1)(2,3));
複製代碼
//案例:提取字符串的空白字符
const match = curry(function (reg, str) {
return str.match(reg);
});
const haveSpace = match(/\s+/g);
const haveNumber = match(/\d+/g);
const filter = curry(function(func,arry){
return arry.filter(func);
});
console.log(haveSpace('hello world'));
console.log(haveNumber('123abc'));
console.log(filter(haveSpace,['jonm Connm','Jone_Done']));
const findSpace = filter(haveSpace);//新的函數 查找數組中具備空白數組的函數
console.log(findSpace(['jonm Connm','Jone_Done']));
複製代碼
閉包的本質就是內部函數能夠訪問外部函數的成員,而柯里化解決的是函數多個參數將函數進行分解的最小粒度的問題。要注意閉包和柯里化的區別兩個不是一個概念。
//柯里化原理實現
function curry(func) {
return function curriedFn(...args) {
//判斷匿名接受的參數個數以及func的形參個數
if (args.length < func.length) {
//只傳遞部分的參數則返回一個新的函數
return function () {
//再次調用curriedFn 合併參數
return curriedFn(...args.concat(Array.from(arguments)));
}
}
//參數相同的狀況下直接調用func
return func(...args);
}
}
function getSum(a, b, c) {
return a + b + c;
}
const curried = curry(getSum);
console.log(curried(1, 2, 3));
console.log(curried(1, 2)(3));
console.log(curried(1)(2, 3));
複製代碼
這一塊是比較燒腦的,跟着調試工具來進行理解就很是容易理解了,以下圖所示:當執行到curried(1,2)(3)
的時候,能夠看到在Closure
的做用域中有兩個一個是傳入的func
一個是分解的函數傳遞的值args[1,2]
代碼繼續往下執行,會調用curriedFn()
將上一次的參數和此次傳入的(3)
進行合併,這時候arg.length==func.length
,就會調用本來的函數func將全部的參數傳遞給它.
函數組合(compose):若是一個函數要通過多個函數處理才能獲得最終值,這個時候能夠把中間過程的函數合併成一個函數。函數就像是數據的管道,函數組合就是把這些管道鏈接起來,讓數據穿過多個管道造成最終結果。函數組合默認是從右到左執行.
以下例子,演示了函數組合
function compose(f, g) {
return function (value) {
return f(g(value));
}
}
/* 演示函數組合的使用 */
function reverse(arr) {
return arr.reverse();
}
function first(arr) {
return arr[0];
}
const last = compose(first,reverse);
console.log(last([1,2,3,4,5]));
複製代碼
Lodash 中的組合函數,經過flowRight方法對函數進行組合,函數的執行順序從右到左
const _ = require('lodash');
const reverse = arr => arr.reverse();
const first = arr => arr[0];
const toUpper = s => s.toUpperCase();
const l = _.flowRight(toUpper, first, reverse);
console.log(l(['a', 'b', 'c', 'd', 'e']));
複製代碼
下面咱們來看看flowRight 的方法是如何實現的,這裏就要考到API掌握的程度了,數組的reduce
和reverse
因爲數組的執行順序從左到右執行因此要講數組進行反轉調用reverse(
)方法,reduce
方法是遍歷數組將上一個數組元素的值傳遞給下一個數組元素。這樣咱們就實現了組合函數,上一個函數的值傳遞給下一個函數。
//flowRight 的實現方法
function compose(...args) {
console.log(args);
return function (value) {
return args.reverse().reduce(function (acc, fn) {
return fn(acc);
}, value);
}
}
//獲取數組最後一個元素 轉換爲大寫 注意函數的運行順序從右到左
const l = compose(toUpper, first, reverse);
複製代碼
arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue])
第一個累計器累計回調的返回值; 它是上一次調用回調時返回的累積值
第二個參數數組中正在處理的元素。
將compose簡寫:經過ES6箭頭函數簡化代碼
const compose = (...args) => (value) => args.reverse().reduce((acc, fn) =>
fn(acc), value);//reduce 第二個參數是一個初始的值 reduce是將全部數組進行遍歷好比累加第一個的結果會傳入到第二個中
複製代碼
let f = compose(f,g,h);
let a = compose(compose(f,g),h) == compose(f,compose(g,h))
//結合律
const f = _.flowRight(_.flowRight(_.toUpper,_.first),_.reverse);
===
const f = _.flowRight(_.toUpper,_.flowRight(_.first,_.reverse));
console.log(f(['a', 'b', 'c', 'd', 'e']));
複製代碼
組合函數如何調試呢?好比我想打印某個方法執行的結果,其實處理很是簡單咱們只須要在想要打印某個方法的執行結果的方法後面添加一個方法trace
,trace
方法就是提供打印的方法,在該方法中能夠拿到上一個方法的返回值這樣就能夠打印上個一個方法的結果了,以下代碼所示:
/* 函數組合調試 */
//NEVER SAY DIE => never-say-die
const _ = require('lodash');
//_.split();
const split = _.curry((sep, str) => {
return _.split(str, sep);
});
//toLower join
const join = _.curry((sep, arr) => {
return _.join(arr, sep);
});
const trace = _.curry((tag,v)=>{
console.log(tag,v);
return v;
});
const map = _.curry((func,arr)=>{
return _.map(arr,func);
})
const f = _.flowRight(join('-'),trace('map'), map(_.toLower),trace('split'),split(' '));
console.log(f('NEVER SAY DIE'));
複製代碼
解決了上述中要使用curry進行柯里化的問題,有一些自帶的方法是先傳遞數據在傳遞迴調函數的,而fp模塊就是解決這種問題,將數據滯後。(PS:其實不一樣的語言和框架都是爲了解決問題的,請不要忘記程序員的本質就是爲了解決問題)
以下代碼中,通常常見的方法好比map()第一個參數都須要傳遞數據才能夠執行,可是這樣就沒法作到柯里化的處理了,那就必須經過柯里化將該方法從新封裝一層以下代碼:這樣是很是很差的設計,那麼loadsh
是否提供了這樣的解決方案呢?答案是確定的咱們來看fp
模塊
const _ = require('lodash');
//_.split();
const split = _.curry((sep, str) => {
return _.split(str, sep);
});
//toLower join
const join = _.curry((sep, arr) => {
return _.join(arr, sep);
});
const log=function(v){
console.log(v);
return v;
}
const trace = _.curry((tag,v)=>{
console.log(tag,v);
return v;
});
const map = _.curry((func,arr)=>{
return _.map(arr,func);
})
const f = _.flowRight(join('-'),trace('map'), map(_.toLower),trace('split'),split(' '));
console.log('??',f('NEVER SAY DIE'));
複製代碼
以下代碼,fp模塊對map、join、split對了處理,以函數優先數據滯後
const fp = require('lodash/fp');
const f = fp.flowRight(fp.join('-'),fp.map(fp.toLower),fp.split(' '));
console.log(f('NEVER SAY DIE'));//never_say_die
複製代碼
以下代碼,在_.map中對某個數組執行將數組元素轉換爲Number類型,可是結果打印倒是:23 NaN 2
這是爲何呢?parseInt(s: string, radix?: number) radix
進制因此會存在問題致使2被轉換2進制了,而fp模塊的map
只會向parseInt
傳遞一個參數
console.log(_.map(['23','8','10'],parseInt));//23 NaN 2
//parseInt('23',0,array)
//parseInt('8',1,array)
//parseInt('10',2,array)
//fp 模塊就不會出現這種問題
//fp map 的函數的參數只有一個就是處理的參數
console.log(fp.map(parseInt,['23','8','10']));//23 8 10
複製代碼
能夠把數據處理的過程定義成與數據無關的合成運算,不須要用到表明數據的那個參數,只要把簡單的運算步驟合成到一塊兒,在使用這種模式以前須要定義一些輔助的基本運算函數。
PointFree 模式 不須要關心數據
const f = fp.flowRight(fp.join('-'),fp.map(fp.toLower),fp.split(' '));
複製代碼
案例演示,其實PointFree模式就是函數的組合,函數組合不須要處理數據的,返回的新函數來處理數據
//Hello world => hello_world
const fp = require('lodash/fp');
const f = fp.flowRight(fp.replace(/\s+/g,'_'),fp.toLower);//函數組合不須要處理數據
//返回新的函數來處理數據
console.log(f('Hello world'));
複製代碼
下面咱們在寫一個案例來更深刻的理解PointFree模式
//world wild web => W,W,W
//先切割字符串變成數組,map將數組的每個元素轉換爲大寫,map將數組獲取數組的元素的首字母
const firstLetterToUpper = fp.flowRight(fp.join(', '),
fp.map(fp.flowRight(fp.first,fp.toUpper)),fp.split(' '));
console.log(firstLetterToUpper('world wild web'));
複製代碼
函數式編程中如何控制反作用控制在可控的範圍內、異常處理、異步操做等。這些問題引入了函子的概念
Fuctor函子
函子裏面內部維護一個值,這個值永遠不對外暴露,經過map方法來對值進行處理,經過一個鏈式的調用方式。
class Container {
static of(value) {
return new Container(value);
}
constructor(value) {
this._value = value;
}
map(fn) {
return Container.of(fn(this._value));
}
}
let r = Container.of(5)
.map(x => x + 1)
.map(x => x * x);
console.log(r);//Container { _value: 36 }
複製代碼
總結:
存在的問題,在輸入null的時候存在異常,沒法處理異常狀況,那麼如何解決這種的反作用呢?繼續看下面
//演示null undefined的問題
Container.of(null).map(x=>x.toUpperCase());//TypeError: Cannot read property 'toUpperCase' of null
複製代碼
MayBe函子的做用就是能夠對外部的控制狀況作處理
class MayBe {
static of(value) {
return new MayBe(value);
}
constructor(value) {
this._value = value;
}
map(fn) {
return this.isNoting() ? MayBe.of(null) : MayBe.of(fn(this._value));
}
isNoting() {
return this._value === null || this._value === undefined;
}
}
// let r = MayBe.of('hello world').map(x => x.toUpperCase());
// let r = MayBe.of(null).map(x => x.toUpperCase());//MayBe { _value: null }
let r = MayBe.of('hello world')
.map(x => x.toUpperCase())
.map(x => null)
.map(x => x.split(' '));//MayBe { _value: null } 可是那個地方出現了問題呢? 是沒法知道的
//maybe 函子的問題
console.log(r);
複製代碼
MayBe
函子其實就是在容器的內部判斷值是否爲空,若是爲空就返回一個值爲空的函子。可是MayBe
函子沒法知道哪一個地方出現了問題,如法處理異常問題,這就繼續引出了下一個概念。
Either
二者中的任何一個,相似if...else...的處理。異常會讓函數變的不純,Either函子能夠用來作異常處理,這種函子在經常使用的業務開發中會常常用到務必掌握。
以下代碼,定義兩個函子,一個處理正確的結果,一個處理異常的結果,異常的處理直接返回this
class Left {
constructor(value) {
this._value = value;
}
static of(value) {
return new Left(value);
}
map(fn) {
return this;
}
}
class Right {
constructor(value) {
this._value = value;
}
static of(value) {
return new Right(value);
}
map(fn) {
return Right.of(fn(this._value));
}
}
複製代碼
注意相同的輸入在兩個函子中是不一樣的輸出
let r1 = Right.of(12)
.map(x => x + 2);
let l1 = Left.of(12).map(x => x + 2);
console.log(r1,l1);//Right { _value: 14 } Left { _value: 12 }
複製代碼
下面來演示,異常的處理狀況,以下代碼在catch
中調用Left
函子返回錯誤的結果
function parseJson(str){
try {
return Right.of(JSON.parse(str))
} catch (e) {
//出現錯誤的時候 使用Left 由於相同的輸入 獲得相同的輸出
return Left.of({error:e.message});
}
}
//異常狀況的處理
let r = parseJson('{ "name":"zs" }');
console.log(r);//Left { _value: { error: 'Unexpected token n in JSON at position 1' } }
複製代碼
正常的結果處理狀況,經過.map
對下一步的業務邏輯進一步處理
//正確狀況下的處理
let r = parseJson('{ "name":"zs" }').map(x=>x.name.toUpperCase());//處理json將name屬性轉換爲大寫
console.log(r);//Right { _value: { name: 'ZS' } }
複製代碼
IO 函子中的_value是一個函數,這裏把函數做爲值來處理;IO函子能夠把不純的動做存儲到_value中,延遲執行這個不純的操做(惰性執行),包裝當前的操做把不純的操做交個調用者處理
//IO 函子
const fp = require('lodash/fp');
class IO {
static of(value) {
return new IO(function () {
return value;
});
}
constructor(fn) {
this._value = fn;
}
map(fn){
return new IO(fp.flowRight(fn,this._value));
}
}
//調用
let io = IO.of(process).map(p=>p.execPath).map(p=>p.toUpperCase());
console.log(io);
//將組合的函數調用 先執行p.execPath 再執行:p=>p.toUpperCase() 注意map函數的執行順序
console.log(io._value());///Users/prim/.nvm/versions/node/v12.14.0/bin/node 執行方法
///USERS/PRIM/.NVM/VERSIONS/NODE/V12.14.0/BIN/NODE
複製代碼
folktale 是一個標準的函數式編程庫,異步任務的實現過於複雜,使用folktale中的Task來演示.只提供了一些函數式處理的操做:compose、curry等一些函子Task、Either、Maybe等
Task 函子處理異步任務
const { compose, curry } = require('folktale/core/lambda');
const { toUpper, first,split,find } = require('lodash/fp');
const { task } = require('folktale/concurrency/task');
const fs = require('fs');
let f = curry(2, (x, y) => {
return x + y;
})
console.log(f(1, 2));//3
console.log(f(1)(2));//3
//compose 函數組合
let f1 = compose(toUpper, first);
console.log(f1(['one', 'two']));//ONE
function readFile(filename) {
return task(resolver => {
fs.readFile(filename, 'utf-8', (err, data) => {
if (err) {
resolver.reject(err);
}
resolver.resolve(data);
})
});
}
readFile('package.json')
.map(split('\n'))
.map(find(x=>x.includes('version')))
.run()//?? run有什麼用?執行了什麼代碼呢? 是將上述的結果返回給listen嗎?
.listen(
{
onRejected:err=>{
console.log(err);
},
onResolved:data=>{
console.log(data);
}
}
);
複製代碼
Pointed 函子是實現了of靜態方法的函子,of方法是爲了不使用new來建立對象,更深層的含義是of方法用來把值放到上下文Context(把值放到容器中,使用map來處理值)
其實上述將的函子都是Pointed函子。
IO
函子的問題,在業務邏輯遇到函子嵌套的狀況IO(IO(x)); Monad
就是解決函子嵌套問題的。
let readFile = function (filename) {
return new IO(function () {
return fs.readFileSync(filename, 'utf-8');
});
}
let print = function (log) {
return new IO(function(){
console.log(log);
return log;//log = IO(x)
});
}
let cat = fp.flowRight(print,readFile);
let r = cat('package.json')._value()._value(); // IO(IO(x))
console.log(r);//IO { _value: [Function] }
複製代碼
const fp = require('lodash/fp');
const fs = require('fs');
class IO {
static of(value) {
return new IO(function () {
return value;
});
}
constructor(fn) {
this._value = fn;
}
map(fn) {
return new IO(fp.flowRight(fn, this._value));//合併函數返回一個新的函子
}
join(){
//調用_value
return this._value();
}
flatMap(fn){
return this.map(fn).join();//把合併的函數 而後執行合併函數
}
}
let readFile = function (filename) {
return new IO(function () {
return fs.readFileSync(filename, 'utf-8');
});
}
let print = function (log) {
return new IO(function(){
console.log(log);
return log;//log = IO(x)
});
}
let r = readFile('package.json')//_value = fn1
.map(x=>x.toUpperCase())//處理文件 _value=fn11
.flatMap(print)//return IO(value) ==> _value = fp.flowRight(print,fn11,fn1); value = _value();
.join(); // map(fn2) _value = fn2=new IO() ,fn1 join():_value: fp.flowRight(fn2, fn1) => new IO(fn3);---> join:fn3()
console.log(r);//IO { _value: [Function] }
複製代碼