函子(Functor)
函子是一個特殊的容器,通過一個普通物件來實作,該物件具有map方法,map方法可以運行一個函式對值進行處理(變形關系),容器包含值和值變形關系(這個變形關系就是函式),函式式編程中解決副作用的存在
- 函式式編程的運算不直接操作值,,而是由函子完成
- 函子就是一個實作了
map契約的物件 - 我們可以把函子想象成一個盒子,盒子里面封裝了一個值
- 想要處理盒子中的值,我們需要給盒子的
map方法傳遞一個處理值的函式(純函式),由這個函式來對值進行處理 - 最終map方法回傳一個包含新值所在的盒子(函子)
根據函子的定義我們創建一個函子
// functor 函子
class Container {
constructor (value) {
// 函子內部保存這個值,下劃線是不想外部訪問
this._value = https://www.cnblogs.com/fengbaba/archive/2022/10/23/value
}
// map 方法接收一個處理值的函式
map (fn) {
return new Container(fn(this._value))
}
}
此時就已經創建了一個函子但是這是面向物件的方式來創建的,換成用函式式編程來寫一個函子
class Container {
constructor (value) {
this._value = https://www.cnblogs.com/fengbaba/archive/2022/10/23/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)
但是這個函子還是存在一些問題,比如空值的時候就會報錯, 會讓我們的函子變的不純,我們需要去攔截空值錯誤,我們創建一個方法去判斷是否為空值,如果是控制我們直接回傳一個空值的函子,如果有值再去處理,這個時候就需要使用MayBe函子
let x = Container.of(null).map(x => x + 1).map(x => x - 1)
MayBe 函子
我們在編程的程序中可能會遇到很多錯誤,需要對這些錯誤做相應的處理,MayBe函子的作用就是可以對外部的空值情況做處理(控制副作用在允許的范圍)
// MayBe 函子
class MayBe {
constructor (value) {
this._value = https://www.cnblogs.com/fengbaba/archive/2022/10/23/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)
這個時候我們已經能正常執行了,但是現在出現了空值的函子,但是我們不知道那個地方出現了空值,所以我們創建兩個函子一個是正常的處理一個是出現錯誤情況處理,正常的就按照正常的方式創建,錯誤的是是否我們把map方法改造一下讓她不再處理回呼函式,直接回傳一個空值的MayBe函子,這樣就記錄下了錯誤資訊Eitcher 函子就是來處理這種情況的
Either函子
Eitcher 類似于 if else 的處理,兩者中的任何一個,例外會讓函式變的不純,Eitcher函子可以用來做例外處理
// 因為是二選一,所以定義兩個類 Left 和 Right
// 記錄錯誤資訊的
class Left {
constructor (value) {
this._value = https://www.cnblogs.com/fengbaba/archive/2022/10/23/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 })
}
}
// 故意傳入錯誤的資料
let r = parseJson('{ name: "2" }')
r.map(x => x.name.toUpperCase())
console.log(r)
IO 函子
IO 函子中的 _value 是一個函式, 這里把函式作為值來處理, IO 函子可以吧不純的動作儲存到_value中,延遲這個不純的操作(惰性執行),保證當前的操作是純的,延遲把不純的操作到呼叫者來處理
const fp = require('lodash/fp')
// IO 函子
class IO {
constructor (fn) {
this._value = https://www.cnblogs.com/fengbaba/archive/2022/10/23/fn
}
static of (value) {
return new IO(function () {
return value
})
}
map (fn) {
// 把當前的value 和傳入的fn 函陣列合成一個新的函式
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 函子內部幫我們包裝了一些函式,當我們傳遞函式的時候有可能這個函式是一個不純的操作,不管這個函式純與不純,IO這個函子在執行的程序中它回傳的這個結果始終是一個純的操作,我們呼叫map的時候始侄訓傳的是一個函子,但是IO函子這個_value屬性他里面要去合并很多函式,所以他里面可能是不純的,把這些不純的操作延遲到了呼叫的時候,也就是我們通過IO函子控制了副作用的在可控的范圍內發生
實作 liunx 下 cat 命令
const fp = require('lodash/fp')
// IO 函子
class IO {
constructor (fn) {
this._value = https://www.cnblogs.com/fengbaba/archive/2022/10/23/fn
}
static of (value) {
return new IO(function () {
return value
})
}
map (fn) {
// 把當前的value 和傳入的fn 函陣列合成一個新的函式
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())
此時IO函子出現了嵌套的問題,導致呼叫嵌套函子中的方法就必須要要._value()._value() 這樣來執了,嵌套了幾層就需要幾層呼叫
Folktale
Folktale 是一個標準的函式式編程庫,和lodash不同的是,他沒有提供很多功能函式,只提供了一些函式式處理的操作,例如:compose、curry等,一些函子 Task、Either、MayBe等,
Folktale 中的curry 與compose的簡單使用
const { compose, curry } = require('folktale/core/lambda')
const { toUpper, first } = require('lodash/fp')
// 與lodash區別,第一個引數指明后面引數的個數
let f = curry(2, (n1, n2) => n1 + n2)
console.log(f(1, 2))
// compose 就是函陣列合 lodash 中的函陣列合是 flowRight
let f2 = compose(toUpper, first)
console.log(f2(['one', 'two']))
Folktale 中的 task 函子
函子可以處理異步任務,在異步任務中會通往地獄之門的回呼,而使用task 函子可以避免回呼的嵌套,詳細請看官方檔案
// Task 異步任務
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')))
// 執行讀取檔案
.run()
.listen({
onRejected(err) {
console.log(err)
},
onResolved(value) {
console.log(value)
}
})
Pointed函子
Pointed函子 是實作了of靜態方法, of 方法是為了避免使用new 來創建物件,更深層次含義是of方法把值放到背景關系Context(把值放到容器中,使用map 來處理值)
class Container {
constructor (value) {
this._value = https://www.cnblogs.com/fengbaba/archive/2022/10/23/value
}
static of () {
return new Container(value)
}
map (fn) {
return new Container(fn(this._value))
}
}
Monad函子
解決函子嵌套的問題,Monad 函子是可以變扁的 Pointed 函子 IO(IO),一個函子如果具有join和of兩個方法并遵循一些定律就是一個Monad
class IO {
constructor (fn) {
this._value = https://www.cnblogs.com/fengbaba/archive/2022/10/23/fn
}
static of (value) {
return new IO(function () {
return value
})
}
map (fn) {
return new IO(fp.flowRight(fn, this._value))
}
join () {
return this._value()
}
// 同時呼叫 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)
當我們想要去呼叫一個方法,這個方法回傳一值的時候我們去呼叫map方法,當我們想要去呼叫一個方法,這個方法回傳一個函子的時候我們去呼叫flatMap方法
原文地址:https://kspf.xyz/archives/17
更多內容微信公眾號搜索充饑的泡飯
小程式搜一搜開水泡飯的博客
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/519082.html
標籤:其他
上一篇:函式柯里化實作sum函式
