文章內(nèi)容輸出來源:拉勾教育大前端高薪訓(xùn)練營 和自我總結(jié)
學(xué)習(xí)函數(shù)式編程的意義
1.受React的流行而被人們越來越關(guān)注(如:React中的高階組件使用高階函數(shù)實(shí)現(xiàn)的,高階函數(shù)就是函數(shù)式編程的一個特性(非純函數(shù)),React生態(tài)中redux使用了函數(shù)式編程的一些思想)
2.Vue 3 越來越偏向函數(shù)式編程
3.函數(shù)式編程可以拋棄煩人的this
4.在打包過程中可以更好的利用tree shaking過濾掉無用的代碼
5.函數(shù)式編程方便測試以及并行處理
6.提供許多庫(如:lodash,underscore,ramda)幫助我們進(jìn)行函數(shù)式開發(fā)
函數(shù)式編程
函數(shù)式編程(FP)是編程范式(包括面向過程編程、面向?qū)ο缶幊蹋┲?,它是用來描述?shù)據(jù)(函數(shù))之間的映射;是對運(yùn)算過程的抽象,可以讓代碼無數(shù)次重用,亦可讓代碼更加簡潔
函數(shù)式編程中的函數(shù)指的并不是程序中的函數(shù),而是數(shù)學(xué)中的函數(shù)(即映射關(guān)系),它是一個純函數(shù),即對相同的輸入始終得到相同的輸出;這些函數(shù)都是細(xì)粒度的函數(shù),可以把這些函數(shù)組合成功能更強(qiáng)大的函數(shù)
函數(shù)是一等公民(頭等函數(shù))
1.函數(shù)可以存儲在變量
let fn = function(){
console.log('hello world!!')
}
fn()
2.函數(shù)可以作為參數(shù)
好處:它可以讓這個函數(shù)變得更靈活,而且調(diào)用函數(shù)時不用考慮函數(shù)內(nèi)部實(shí)現(xiàn)的細(xì)節(jié),且函數(shù)的名字具有實(shí)際意義
//高階函數(shù)-函數(shù)作為參數(shù)
//forEach-遍歷函數(shù)
function forEach(array,fn) {
for(let i = 0; i < array.length; i++){
fn(array[i])
}
}
//filter-過濾函數(shù)
function filter(array, fn){
let results = []
for(let i = 0; i < array.length; i++){
if(fn(array[i])){
results.push(array[i])
}
}
return results
}
//測試數(shù)組
let arr = [1, 4, 6, 7, 11]
//測試
forEach(arr,function (item) {
console.log(item)
})
let r = filter(arr, function (item) {
return item % 2 === 0
})
console.log(r) //[ 4,6 ]
3.函數(shù)可以作為返回值
//高階函數(shù)-函數(shù)作為返回值
/**讓一個函數(shù)生成一個函數(shù)**/
function makeFn() {
let msg = 'hello function'
return function () {
console.log(msg)
}
}
const fn = makeFn()
fn() //hello function
makeFn()() //hello function
/**應(yīng)用**/
//once--函數(shù)只執(zhí)行一次
function once(fn) {
let done = false
return function () {
if(!done){
done = true
return fn.apply(this, arguments)
}
}
}
let pay = once(function (money) {
console.log(`支付了:${money} RMB`)
})
pay(3) //支付了:3 RMB
pay(3)
pay(3)
pay(3)
高階函數(shù)(Higher-order function)
高階函數(shù):函數(shù)可以作為參數(shù)傳遞給另一個函數(shù)、函數(shù)也可以作為另一個函數(shù)的返回結(jié)果(代碼實(shí)現(xiàn)在上面)
高階函數(shù)的意義:它是用來抽象通用的問題,抽象可以幫我們屏蔽細(xì)節(jié),只需要關(guān)注于我們的目標(biāo)
常用的高階函數(shù)
forEach / map / filter / every / some / find / finfIndex / reduce / sort / ...
(以上是數(shù)組的一些方法,都需要函數(shù)作為參數(shù))
// 模擬常用的高階函數(shù):map、every、some
//函數(shù)作為參數(shù)可以讓函數(shù)變得更靈活
//map
const map = (array, fn) => {
let results = []
for(let value of array){
results.push(fn(value))
}
return results
}
//every
const every = (array, fn) => {
let result = true
for (let value of array){
result = fn(value)
if(!result){
break
}
}
return result
}
//some
const some = (array, fn ) => {
let result = false
for (let value of array){
result = fn(value)
if(result){
break
}
}
return result
}
//測試數(shù)組
let arr = [1, 2, 3, 4]
//測試
let a = map(arr,v => v * v)
console.log(arr) //[ 1, 4, 9, 16 ]
let r = every(arr,v => v > 0)
console.log(r) //true
let s = some(arr, v => v >9)
console.log(s) //false
閉包(Closure)
閉包是指可以在另一個作用域中調(diào)用一個函數(shù)的內(nèi)部函數(shù)并訪問到該函數(shù)的作用域中的成員,它是由函數(shù)和周圍的狀態(tài)(詞法環(huán)境)的引用捆綁在一起形成的,它的好處是延長了外部函數(shù)的內(nèi)部變量的作用域范圍
閉包的本質(zhì):函數(shù)在執(zhí)行的時候會放到一個執(zhí)行棧上,當(dāng)函數(shù)執(zhí)行完畢之后會從執(zhí)行棧上移除,但是堆上的作用域成員因?yàn)楸煌獠恳枚荒茚尫?/strong>,因此內(nèi)部函數(shù)依然可以訪問外部函數(shù)的成員
閉包-案例
// Math.pow(4, 2)
// Math.pow(5, 2)
function makePower(power) {
return function (number) {
return Math.pow(number, power)
}
}
// 求平方和三次方
let power2 = makePower(2)
let power3 = makePower(3)
// 測試
console.log(power2(4)) // 16
console.log(power2(5)) // 25
console.log(power3(4)) // 64
注:閉包發(fā)生是在內(nèi)部函數(shù)被調(diào)用的時候發(fā)生的
純函數(shù)
純函數(shù):相同的輸入永遠(yuǎn)得到相同的輸出,而且沒有任何可觀察的副作用;純函數(shù)類似于數(shù)學(xué)中的函數(shù)
// 純函數(shù)和不純函數(shù)
// slice /splice
// 測試數(shù)組
let array = [1, 2, 3, 4, 5, 6]
// 純函數(shù)
console.log(array.slice(0, 3)) // [ 1, 2, 3 ]
console.log(array.slice(0, 3)) // [ 1, 2, 3 ]
console.log(array.slice(0, 3)) // [ 1, 2, 3 ]
// 不純函數(shù)
console.log(array.splice(0, 3 )) // [ 1, 2, 3 ]
console.log(array.splice(0, 3 )) // [ 4, 5, 6 ]
console.log(array.splice(0, 3 )) // []
// 純函數(shù)
function getSum(n1, n2) {
return n1 + n2
}
console.log(getSum(3, 4)) // 7
console.log(getSum(3, 4)) // 7
console.log(getSum(3, 4)) // 7
函數(shù)式編程不會保留計(jì)算中間的結(jié)果,所以變量是不可變的(無狀態(tài)的)
可以把一個函數(shù)的執(zhí)行的結(jié)果交給另一個函數(shù)去處理(將細(xì)粒度的函數(shù)傳給那些函數(shù)庫)
lodash純函數(shù)庫
// 演示lodash
// first / last / toUpper / reverse / each / includes / find / findIndex
/**
* 安裝lodash
* 1.先生成一個package.json文件 --- npm init -y
* 2.安裝 npm i lodash
* **/
// 引入lodash
const _ = require('lodash')
//測試數(shù)組
const array = ['jack', 'tom', 'lucy', 'kate']
//測試
console.log(_.first(array)) // jack
console.log(_.last(array)) //kate
console.log(_.toUpper(_.last(array))) // KATE
/*注意:這里reverse內(nèi)部調(diào)用的即是數(shù)組中的reverse,數(shù)組中的reverse方法會改變原數(shù)組,它不是一個純函數(shù)*/
console.log(_.reverse(array)) // [ 'kate', 'lucy', 'tom', 'jack' ]
const r =_.each(array,(item, index) => {
console.log(item, index)
})
console.log(r)
// kate 0
// lucy 1
// tom 2
// jack 3
純函數(shù)緩存案例
// 記憶函數(shù)
const _ = require('lodash')
function getArea(r) {
console.log(r)
return Math.PI * r * r
}
//模擬 memoize 實(shí)現(xiàn)
function memoize(fn) {
let cache = {}
return function () {
let key = JSON.stringify(arguments)
cache[key] = cache[key] || fn.apply(fn, arguments)
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
純函數(shù)的好處
- 可緩存
- 因?yàn)榧兒瘮?shù)對相同的輸入始終有相同的結(jié)果,所以可以把純函數(shù)的結(jié)果緩存起來
- 可測試
- 純函數(shù)讓測試更加方便(純函數(shù)始終有輸出和輸出,可以斷點(diǎn)查看)
- 并行處理
- 在多線程環(huán)境下并行操作共享內(nèi)存數(shù)據(jù)很可能會出現(xiàn)意外情況(操作全局變量情況,多個線程操作同一個變量)
- 純函數(shù)不需要訪問共享的內(nèi)存數(shù)據(jù),所以在并行環(huán)境下可以任意u運(yùn)行純函數(shù)(ES6以后新增了Web Worker)
純函數(shù)副作用
純函數(shù):對于相同的輸入永遠(yuǎn)會得到相同的輸出,而且沒有任何可觀察的副作用
// 不純的,函數(shù)的返回值依賴外部的變量
let mini = 18
function checkAge (age) {
return age >= mini
}
// 純的(有硬編碼,后續(xù)可以通過柯里化解決)
function checkAge2 (age) {
let mini = 18
return age >= mini
}
副作用會讓一個函數(shù)百年的不純(如上面例子),純函數(shù)是由相同的輸入返回相同的輸出,如果函數(shù)依賴于外部的狀態(tài)就無法保證相同的輸出,那么就會帶來副作用
副作用的主要來源:
- 配置文件
- 數(shù)據(jù)庫
- 獲取用戶的輸入(賬號密碼等...)
-
......
所有的外部交互都有可能產(chǎn)生副作用,副作用也使得方法通用性下降不適合擴(kuò)張和可重用性,同時副作用會給程序帶來安全隱患,從而給程序帶來不確定性,但是副作用不可能完全禁止,盡可能控制他們在可控范圍內(nèi)發(fā)生
柯里化
柯里化使用:
- 當(dāng)一個函數(shù)有多個參數(shù)的時候,先傳遞一部分參數(shù)給它(這部分參數(shù)在以后永遠(yuǎn)不變)
- 然后返回一個新的函數(shù),用來接收剩余的參數(shù),并處理后返回結(jié)果
使用柯里化解決上面硬編碼的問題
// 柯里化演示
/*
function checkAge(age) {
let min = 18
return age >= 18
}*/
//普通的純函數(shù)
function checkAge(min, age) {
return age >= min
}
console.log(checkAge(18,20)) //true
console.log(checkAge(18,24)) //true
console.log(checkAge(20,24)) //true
// 函數(shù)的柯里化
function checkAgeAnother(min) {
return function (age) {
return age >= min
}
}
let checkAge18 = checkAgeAnother(18)
let checkAge20 = checkAgeAnother(20)
console.log(checkAge18(17)) //false
console.log(checkAge18(24)) //true
// ES6寫法
let checkAgeEs6 = min => (age => age >= min)
let checkAgeEs618 = checkAgeEs6(18)
console.log(checkAgeEs618(20) //true
Lodash中的柯里化函數(shù)
_curry(func)
- 功能:創(chuàng)建一個函數(shù),該函數(shù)接收一個或多個func的參數(shù),如果func所需的參數(shù)都已經(jīng)被提供則執(zhí)行func并返回執(zhí)行的結(jié)果,否則繼續(xù)返回該函數(shù)并等待接收func所需的剩余參數(shù)
- 參數(shù):需要柯里化的函數(shù)
- 返回值:柯里化后的函數(shù)
// lodash 中的 curry 基本使用
/**最重要的一點(diǎn)是:可以將任意多個參數(shù)的函數(shù)轉(zhuǎn)換成一元函數(shù)**/
const _ = require('lodash')
function getSum(a, b, c) {
return a + b + c
}
const curried = _.curry(getSum)
console.log(curried(1, 2, 3)) // 6
console.log(curried(1)(2,3)) // 6
console.log(curried(1,2)(3)) // 6
案列
// 柯里化案例
// ''.match(/\s+/g)
// ''.match(/\d+/g)
const _ = require('lodash')
const match = _.curry(function (reg, str) {
return str.match(reg)
})
const haveSpace = match(/\s+/g)
const haveNumber = match(/\d+/g)
console.log(haveSpace('hello world')) // [ ' ' ]
console.log(haveSpace('helloworld')) // null
console.log(haveNumber('123abc')) // [ '123' ]
console.log(haveNumber('abc')) //null
const filter = _.curry(function (fn, array) {
return array.filter(fn)
})
const filterEs6 = _.curry((fn,array) => array.filter(fn))
console.log(filter(haveSpace,['John Connor', 'John_Donne'])) //[ 'John Connor' ]
console.log(filterEs6(haveSpace,['John Connor', 'John_Donne'])) //[ 'John Connor' ]
柯里化原理模擬
// 模擬實(shí)現(xiàn) lodash 中的curry方法
// const _ = require('lodash')
function getSum(a, b, c) {
return a + b + c
}
const curried = curry(getSum)
console.log(curried(1, 2, 3)) // 6
console.log(curried(1)(2,3)) // 6
console.log(curried(1,2)(3)) // 6
function curry(fn) {
return function curriedFn(...args) {
if(args.length < fn.length) {
return function () {
return curriedFn(...args.concat(Array.from(arguments)))
//或借鑒別人的寫法 return curriedFn(..args, ...arguments)
}
}else{
return fn(...args)
}
}
}
柯里化總結(jié)
- 柯里化可以讓我們給一個函數(shù)傳遞較少的參數(shù)得到一個已經(jīng)記住了某些固定參數(shù)的新函數(shù)
- 這是一種對參數(shù)的‘緩存’
- 讓函數(shù)變得更靈活,讓函數(shù)的粒度更小
- 可以把多元函數(shù)轉(zhuǎn)換成一元函數(shù),可以使用組合函數(shù)生成功能更強(qiáng)大的函數(shù)
函數(shù)組合(Compose)
一個函數(shù)如果要經(jīng)過多個函數(shù)處理才能得到最終值,這個時候可以把中間過程的函數(shù)合并成一個函數(shù);函數(shù)組合可以讓我們把細(xì)粒度的函數(shù)重新組合生成一個新的函數(shù)
- 函數(shù)就像是數(shù)據(jù)的管道,函數(shù)組合就是把這些管道連接起來,讓數(shù)據(jù)穿過多個管道形成最終結(jié)果
- 函數(shù)組合默認(rèn)是從右到左執(zhí)行
// 函數(shù)組合演示
function compose(f, g) {
return function(value){
return f(g(value))
}
}
function reverse(array) {
return array.reverse()
}
function first(array){
return array[0]
}
const last = compose(first, reverse)
console.log(last([1, 2, 3])) // 3
Lodash中的組合函數(shù)
- lodash中組合函數(shù)flow()或則flowRight(),他們都可以組合多個函數(shù)
- flow()是從左到右運(yùn)行
- flowRight() 是從右到左執(zhí)行,使用的更多一些
// lodash 中的函數(shù)組合方法 _.flowRight()
const _ = require('lodash')
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
const f = _.flowRight( toUpper, first, reverse)
console.log(f(['one', 'two', 'three'])) //THREE
flowRight組合函數(shù)原理模擬
// flowRight組合函數(shù)原理模擬
const reverse = arr => arr.reverse()
const first = arr => arr[0]
const toUpper = s => s.toUpperCase()
/*function compose(...args) {
return function(value){
return args.reverse().reduce(function (acc, fn) {
return fn(acc)
},value)
}
}*/
//使用箭頭函數(shù)改造
const compose =(...args) => value => args.reverse().reduce((acc,fn) => fn(acc),value)
const f = compose( toUpper, first, reverse)
console.log(f(['one', 'two', 'three'])) //THREE
函數(shù)的組合要滿足結(jié)合律
// 組合函數(shù)結(jié)合律測試
const _ = require('lodash')
const f = _.flowRight( _.toUpper, _.first, _.reverse)
console.log(f(['one', 'two', 'three'])) //THREE
const f1 = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse)
console.log(f1(['one', 'two', 'three'])) //THREE
const f2 = _.flowRight(_.toUpper,_.flowRight( _.first, _.reverse))
console.log(f2(['one', 'two', 'three'])) //THREE
函數(shù)組合調(diào)試方法
// 函數(shù)組合 調(diào)試
// NEVER SAY DIE ---> never-say-die
const _ = require('lodash')
const log = v => {
console.log(v)
return v
}
const trace = _.curry((tag, v) => {
console.log(tag, v)
return v
})
// _.split()
const split = _.curry((sep, str) => _.split(str, sep))
//_.toLower()
// _.join()
const join = _.curry((sep,str) => _.join(str, sep))
// _.map()
const map = _.curry((fn, array) => _.map(array, fn))
const f = _.flowRight( join('-'), trace('map 之后'), map(_.toLower), trace('map 之前'), split(' '))
console.log(f('NEVER SAY DIE')) // never-say-die
Lodash-fp模塊
- lodash的fp模塊提供了實(shí)用的對函數(shù)式編程友好的方法, 函數(shù)優(yōu)先,數(shù)據(jù)在后
- 提供了不可變auto-curried iteratee-first data-last的方法
//lodash 的 fp 模塊
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
lodash與lodash-fp中map的區(qū)別
//lodash與lodash-fp中map的區(qū)別
const _ = require('lodash')
// lodash中的map中的函數(shù)的參數(shù)有三個:(item, index, array)
console.log(_.map(['23', '5', '10'],parseInt)) // [ 23, NaN, 2 ]
// parseInt('23', 0, array) 第二個參數(shù)是0,則是10進(jìn)制
// parseInt('5', 1, array) 第二個參數(shù)是1,不合法,輸出NaN
// parseInt('10', 2, array) 第二個參數(shù)是2,表示2進(jìn)制,輸出2
// lodash-fp中的map中的函數(shù)的參數(shù)有1個:(item)
const fp = require('lodash/fp')
console.log(fp.map(parseInt, ['23', '5', '10'])) //[ 23, 5, 10 ]
Pointfree模式
Point Free是一種編程的風(fēng)格,它的具體實(shí)現(xiàn)是函數(shù)組合,它更抽象些;我們可以把數(shù)據(jù)處理的過程定義為與數(shù)據(jù)無關(guān)的合成運(yùn)算,把需要用到代表數(shù)據(jù)的那個參數(shù),只要把簡單的運(yùn)算步驟合成在一起,在使用這種模式之前我們需要定義一些輔助的基本運(yùn)算函數(shù)
- 不需要指明處理的數(shù)據(jù)
- 只需要合成運(yùn)算過程
- 需要定義一些輔助的基本運(yùn)算函數(shù)
// point free實(shí)現(xiàn)即為函數(shù)的組合
// Hello World => hello_world
const fp = require('lodash/fp')
const f = fp.flowRight(fp.replace(/\s+/g,'_'),fp.toLower)
console.log(f('Hello World')) // hello_world
point free案例
//把一個字符串的首字母轉(zhuǎn)換成大寫,使用.作為分隔符
// world wild web =>W.W.W
const fp = require('lodash/fp')
// const firstLetterToUpper = fp.flowRight( fp.join('.') , fp.map(fp.first) , fp.map(fp.toUpper), fp.split(' '))
// 改進(jìn)
const firstLetterToUpper = fp.flowRight( fp.join('.') , fp.map(fp.flowRight(fp.first, fp.toUpper)), fp.split(' '))
console.log(firstLetterToUpper('world wild web')) //W.W.W
函子(Functor)
什么是函子
- 容器:包含值和值的變形關(guān)系(這個變形關(guān)系就是函數(shù))
- 函子:是一個特殊的容器,通過一個普通的對象來實(shí)現(xiàn),該對象具有map方法,map方法可以運(yùn)行一個函數(shù)對值進(jìn)行處理(變形關(guān)系)
//Functor 函子
class Container {
constructor(value) {
this._value = value
}
map(fn){
return new Container(fn(this._value))
}
}
let r = new Container(5)
.map(x => x + 1)
.map(x => x * x)
console.log(r) // Container { _value: 36 }
更換為靜態(tài)方法創(chuàng)建對象
//Functor 函子
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 + 2)
.map(x => x * x)
.map(x => console.log(x)) // 49
console.log(r) // Container { _value: 49 }
函子總結(jié)
- 函數(shù)式編程的運(yùn)算不直接操作值,而是由函子完成
- 函子就是一個實(shí)現(xiàn)了map契約的對對象
- 我們可以把函子想象成一個盒子,這個盒子封裝了一個值
- 想要處理盒子中的值,我么需要給盒子的map方法傳遞一個處理值的函數(shù)(純函數(shù)),由這個函數(shù)來對值進(jìn)行處理
- 最終map方法返回一個包含心智的盒子(函子)
MayBe 函子
MayBe函子的作用就是可以對外部的空值情況(這種情況下普通函子就會變得不純)做處理(控制副作用在允許的范圍內(nèi))
//MayBe函子
class MayBe{
static of(value){
return new MayBe(value)
}
constructor(value) {
this._value =value
}
map(fn){
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
}
isNothing(){
return this._value === null || this._value === undefined
}
}
let r = MayBe.of('Hello World')
.map(x => x.toUpperCase())
console.log(r) //MayBe { _value: 'Hello World' }
let y = MayBe.of(null)
.map(x => x.toUpperCase())
console.log(y) //MayBe { _value: null }
let z = MayBe.of('hello world')
.map(x => x.toUpperCase())
.map(x => null)
.map(x => x.split(' '))
console.log(z) //MayBe { _value: null } //缺陷:無法知道null是哪里發(fā)生的
Either 函子
- Either兩者中的任何一個,類似于if...else...的處理
- 異常會讓函數(shù)變得不純,Either函子可以用來做異常處理
// Either 函子
class Left{
static of(value){
return new Left(value)
}
constructor(value) {
this._value =value
}
map(fn){
return this
}
}
class Right{
static of(value){
return new Right(value)
}
constructor(value) {
this._value =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) //Right { _value: 14 }
console.log(l1) //Left { _value: 12 }
function parseJson(str) {
try{
return Right.of(JSON.parse(str))
}catch (e) {
return Left.of({error: e.message})
}
}
let m = parseJson('{name: zs}')
let n = parseJson('{"name": "zs"}')
let z = parseJson('{"name": "zs"}').map(x => x.name.toUpperCase())
console.log(m); //Left { _value: { error: 'Unexpected token n in JSON at position 1' } }
console.log(n); //Right { _value: { name: 'zs' } }
console.log(z); //Right { _value: 'ZS' }
IO 函子
- IO函子中的_value是一個函數(shù),這里是把函數(shù)作為值來處理
- IO函子可以把不純的動作存儲到_value中,延遲執(zhí)行這個不純的操作(惰性執(zhí)行),包裝當(dāng)前的操作純
- 把不純的操作交給調(diào)用者處理
// 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))
}
}
//調(diào)用
let r = IO.of(process).map(p => p.execPath)
console.log(r) // IO { _value: [Function] }
console.log(r._value()) // F:\developmentEnvironment\nodejs\node.exe
Folktale 函子
Task異步執(zhí)行
- 異步任務(wù)的實(shí)現(xiàn)過于復(fù)雜,我們使用folktale中的Task來演示
- folktale一個標(biāo)準(zhǔn)的函數(shù)式編輯庫
- 和lodash、ramda不同的是,他沒有提供很多功能函數(shù)
- 只提供了一些函數(shù)式處理的操作,例如:compose、curry等,一些函子Task、Either、MayBe等
// folktale 中的 compose、 curry
const {compose, curry} = require('folktale/core/lambda')
const {toUpper, first} = require('lodash/fp')
let f = curry(2, (x, y) => x + y)
console.log(f(1,2)) // 3
console.log(f(1)(2)) // 3
// folktale中的compose相當(dāng)于lodash中的flowRight
let g = compose(toUpper, first)
console.log(g(['one','two'])) //ONE
folktale(2.3.2)2.x中的Task和1.0中的Task區(qū)別很大,1.0這種的用法更接近我們現(xiàn)在演示的函子,下面例子使用2.3來演示
// Task 處理異步任務(wù)
const fs = require('fs')
const {task} = require('folktale/concurrency/task')
const {split, find} = require('lodash/fp')
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()
.listen({
onRejected: err => {
console.log(err)
},
onResolved:value => {
console.log(value) // "version": "1.0.0",
}
})
Pointed函子
- Pointed函子是實(shí)現(xiàn)了of靜態(tài)方法的函子
- of方法是為了避免使用new來創(chuàng)建對象,更深層的含義是of方法用來把值放到上下文Context(把值放到容器中,使用map來處理值)
class Container {
static of (value) {
return new Container(value)
}
constructor(value) {
this._value = value
}
map (fn) {
return Container.of(fn(this._value))
}
}
Monad函子
- Monad函子是可以變扁的Pointed函子,IO(IO(x))
- 一個函子如果由join和of兩個方法并遵守一些定了就是一個Monad
const fp = require('lodash')
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 () {
return this._value()
}
// 當(dāng)fn返回一個函子的時候,用flatMap拍平
flatMap (fn) {
return this.map(fn).join()
}
}
const readFile = function (filename) {
return new IO(function () {
return fs.readFileSync(filename, 'utf-8')
})
}
const print = function (x) {
return new IO(function () {
console.log(x)
return x
})
}
// const cat = fp.flowRight(print, readFile)
// // IO(IO(x))
// // const r = cat('package.json')._value() // IO { _value: [Function (anonymous)] }
// const r = cat('package.json')._value()._value()
const r = readFile('package.json')
.map(fp.toUpper)
.flatMap(print)
.join()
console.log(r)