函子是一個(gè)特殊的容器,通過(guò)一個(gè)普通對(duì)象來(lái)實(shí)現(xiàn),該對(duì)象具有map
方法,map
方法可以運(yùn)行一個(gè)函數(shù)對(duì)值進(jìn)行處理(變形關(guān)系),容器
包含值和值變形關(guān)系(這個(gè)變形關(guān)系就是函數(shù))。函數(shù)式編程中解決副作用的存在
網(wǎng)站建設(shè)哪家好,找成都創(chuàng)新互聯(lián)公司!專注于網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、微信平臺(tái)小程序開(kāi)發(fā)、集團(tuán)企業(yè)網(wǎng)站建設(shè)等服務(wù)項(xiàng)目。為回饋新老客戶創(chuàng)新互聯(lián)還提供了建鄴免費(fèi)建站歡迎大家使用!
map
契約的對(duì)象map
方法傳遞一個(gè)處理值的函數(shù)(純函數(shù)),由這個(gè)函數(shù)來(lái)對(duì)值進(jìn)行處理根據(jù)函子的定義我們創(chuàng)建一個(gè)函子
// functor 函子
class Container {
constructor (value) {
// 函子內(nèi)部保存這個(gè)值。下劃線是不想外部訪問(wèn)
this._value = value
}
// map 方法接收一個(gè)處理值的函數(shù)
map (fn) {
return new Container(fn(this._value))
}
}
此時(shí)就已經(jīng)創(chuàng)建了一個(gè)函子但是這是面向?qū)ο蟮姆绞絹?lái)創(chuàng)建的,換成用函數(shù)式編程來(lái)寫(xiě)一個(gè)函子
class Container {
constructor (value) {
this._value = value
}
map (fn) {
return Container.of(fn(this._value))
}
static of (value) {
return new Container(value)
}
}
let x = Container.of(5).map(x => x + 1).map(x => x - 1)
但是這個(gè)函子還是存在一些問(wèn)題,比如空值的時(shí)候就會(huì)報(bào)錯(cuò), 會(huì)讓我們的函子變的不純,我們需要去攔截空值錯(cuò)誤,我們創(chuàng)建一個(gè)方法去判斷是否為空值,如果是控制我們直接返回一個(gè)空值的函子,如果有值再去處理,這個(gè)時(shí)候就需要使用MayBe
函子
let x = Container.of(null).map(x => x + 1).map(x => x - 1)
我們?cè)诰幊痰倪^(guò)程中可能會(huì)遇到很多錯(cuò)誤,需要對(duì)這些錯(cuò)誤做相應(yīng)的處理,MayBe
函子的作用就是可以對(duì)外部的空值情況做處理(控制副作用在允許的范圍)
// MayBe 函子
class MayBe {
constructor (value) {
this._value = value
}
map (fn) {
return this.isNothing() ? MayBe.of(null) : MayBe.of(fn(this._value))
}
isNothing () {
return this._value === undefined || this._value === null
}
static of (value) {
return new MayBe(value)
}
}
let x = MayBe.of(null)
.map(x => x + 1)
.map(x => x - 1)
console.log(x)
這個(gè)時(shí)候我們已經(jīng)能正常執(zhí)行了,但是現(xiàn)在出現(xiàn)了空值的函子,但是我們不知道那個(gè)地方出現(xiàn)了空值,所以我們創(chuàng)建兩個(gè)函子一個(gè)是正常的處理一個(gè)是出現(xiàn)錯(cuò)誤情況處理,正常的就按照正常的方式創(chuàng)建,錯(cuò)誤的是是否我們把map
方法改造一下讓她不再處理回調(diào)函數(shù),直接返回一個(gè)空值的MayBe
函子,這樣就記錄下了錯(cuò)誤信息Eitcher
函子就是來(lái)處理這種情況的
Eitcher
類似于 if else
的處理,兩者中的任何一個(gè),異常會(huì)讓函數(shù)變的不純,Eitcher
函子可以用來(lái)做異常處理
// 因?yàn)槭嵌x一,所以定義兩個(gè)類 Left 和 Right
// 記錄錯(cuò)誤信息的
class Left {
constructor (value) {
this._value = value
}
map (fn) {
return this
}
static of (value) {
return new Left(value)
}
}
// 正常處理
class Rgiht {
constructor (value) {
this._value = value
}
map (fn) {
return Rgiht.of(fn(this._value))
}
static of (value) {
return new Rgiht(value)
}
}
function parseJson (str) {
try {
return Rgiht.of(JSON.parse(str))
} catch (err) {
return Left.of({ message: err.message })
}
}
// 故意傳入錯(cuò)誤的數(shù)據(jù)
let r = parseJson('{ name: "2" }')
r.map(x => x.name.toUpperCase())
console.log(r)
IO
函子中的 _value
是一個(gè)函數(shù), 這里把函數(shù)作為值來(lái)處理, IO 函子可以吧不純的動(dòng)作儲(chǔ)存到_value
中,延遲這個(gè)不純的操作(惰性執(zhí)行),保證當(dāng)前的操作是純的,延遲把不純的操作到調(diào)用者來(lái)處理
const fp = require('lodash/fp')
// IO 函子
class IO {
constructor (fn) {
this._value = fn
}
static of (value) {
return new IO(function () {
return value
})
}
map (fn) {
// 把當(dāng)前的value 和傳入的fn 函數(shù)組合成一個(gè)新的函數(shù)
return new IO(fp.flowRight(fn, this._value))
}
}
let r = IO.of(process).map(x => x.execPath)
console.log(r)
console.log(r._value())
IO 函子內(nèi)部幫我們包裝了一些函數(shù),當(dāng)我們傳遞函數(shù)的時(shí)候有可能這個(gè)函數(shù)是一個(gè)不純的操作,不管這個(gè)函數(shù)純與不純,IO這個(gè)函子在執(zhí)行的過(guò)程中它返回的這個(gè)結(jié)果始終是一個(gè)純的操作,我們調(diào)用map
的時(shí)候始終返回的是一個(gè)函子,但是IO
函子這個(gè)_value
屬性他里面要去合并很多函數(shù),所以他里面可能是不純的,把這些不純的操作延遲到了調(diào)用的時(shí)候,也就是我們通過(guò)IO
函子控制了副作用的在可控的范圍內(nèi)發(fā)生
實(shí)現(xiàn) liunx 下 cat 命令
const fp = require('lodash/fp')
// IO 函子
class IO {
constructor (fn) {
this._value = fn
}
static of (value) {
return new IO(function () {
return value
})
}
map (fn) {
// 把當(dāng)前的value 和傳入的fn 函數(shù)組合成一個(gè)新的函數(shù)
return new IO(fp.flowRight(fn, this._value))
}
}
let r = IO.of(process).map(x => x.execPath)
function readFile (fileName) {
return new IO(() => fs.readFileSync(fileName, 'utf-8'))
}
function print (x) {
return new IO(() => {
console.log(x)
return x
})
}
let cat = fp.flowRight(print, readFile)
console.log(cat('package.json')._value()._value())
此時(shí)IO
函子出現(xiàn)了嵌套的問(wèn)題,導(dǎo)致調(diào)用嵌套函子中的方法就必須要要._value()._value()
這樣來(lái)執(zhí)了,嵌套了幾層就需要幾層調(diào)用
Folktale 是一個(gè)標(biāo)準(zhǔn)的函數(shù)式編程庫(kù),和lodash
不同的是,他沒(méi)有提供很多功能函數(shù),只提供了一些函數(shù)式處理的操作,例如:compose、curry
等,一些函子 Task、Either、MayBe
等,
Folktale 中的curry
與compose
的簡(jiǎn)單使用
const { compose, curry } = require('folktale/core/lambda')
const { toUpper, first } = require('lodash/fp')
// 與lodash區(qū)別,第一個(gè)參數(shù)指明后面參數(shù)的個(gè)數(shù)
let f = curry(2, (n1, n2) => n1 + n2)
console.log(f(1, 2))
// compose 就是函數(shù)組合 lodash 中的函數(shù)組合是 flowRight
let f2 = compose(toUpper, first)
console.log(f2(['one', 'two']))
函子可以處理異步任務(wù),在異步任務(wù)中會(huì)通往地獄之門(mén)的回調(diào),而使用task
函子可以避免回調(diào)的嵌套,詳細(xì)請(qǐng)看官方文檔
// Task 異步任務(wù)
const { task } = require('folktale/concurrency/task')
const { split, find } = require('lodash/fp')
const fs = require('fs')
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')))
// 執(zhí)行讀取文件
.run()
.listen({
onRejected(err) {
console.log(err)
},
onResolved(value) {
console.log(value)
}
})
Pointed函子 是實(shí)現(xiàn)了of靜態(tài)方法, of 方法是為了避免使用new 來(lái)創(chuàng)建對(duì)象,更深層次含義是of方法把值放到上下文Context
(把值放到容器中,使用map
來(lái)處理值)
class Container {
constructor (value) {
this._value = value
}
static of () {
return new Container(value)
}
map (fn) {
return new Container(fn(this._value))
}
}
解決函子嵌套的問(wèn)題,Monad
函子是可以變扁的 Pointed
函子 IO(IO)
,一個(gè)函子如果具有join
和of
兩個(gè)方法并遵循一些定律就是一個(gè)Monad
class IO {
constructor (fn) {
this._value = fn
}
static of (value) {
return new IO(function () {
return value
})
}
map (fn) {
return new IO(fp.flowRight(fn, this._value))
}
join () {
return this._value()
}
// 同時(shí)調(diào)用 join 和 map
flatMap (fn) {
return this.map(fn).join()
}
}
function readFile (fileName) {
return new IO(() => fs.readFileSync(fileName, 'utf-8'))
}
function print (x) {
return new IO(() => {
return x
})
}
let r = readFile('package.json').flatMap(print).join()
console.log(r)
當(dāng)我們想要去調(diào)用一個(gè)方法,這個(gè)方法返回一值的時(shí)候我們?nèi)フ{(diào)用map
方法,當(dāng)我們想要去調(diào)用一個(gè)方法,這個(gè)方法返回一個(gè)函子的時(shí)候我們?nèi)フ{(diào)用flatMap
方法
原文地址:https://kspf.xyz/archives/17
更多內(nèi)容微信公眾號(hào)搜索充饑的泡飯
小程序搜一搜開(kāi)水泡飯的博客