楔子
今天再來進階一下改寫 Monad compose
Monad
特徵
- of: a => M(a) 也有人叫 lift 或 type lift
- map: map 的 f :: a => M(b) 會變成 M(M(b))
- flatten: M(M(b)) => M(b)
Monad 有 flapMap 的概念
- flatMap = Map + flatten : f(a).flatMap(g) => M(b)
const MyMonad = value => ({
flatMap: f => f(value),
map (f) {
return this.flatMap(a => Monad.of(f(a))),
},
})
Monad.of = x => Monad(x)
Monad(21).map( x => x * 2).map( x => console.log(x))
來舉個 Identity Monad 的例子
{ // Identity monad
const Id = value => ({
// Functor mapping
// Preserve the wrapping for .map() by
// passing the mapped value into the type
// lift:
map: f => Id.of(f(value)),
// Monad chaining
// Discard one level of wrapping
// by omitting the .of() type lift:
chain: f => f(value),
// Just a convenient way to inspect
// the values:
toString: () => `Id(${ value })`
})
// The type lift for this monad is just
// a reference to the factory.
Id.of = Id
Promise 是一個蠻特別的例子,如果用 promises 來看, .flapMap
像什麼? 像不像 .then
promise.then(f) 的 f 何時會被呼叫?是當 .then 之後才會執行 f()
拿下面的例子來看,result 也是個 promise (等同於 M(b))
{
const x = 20 // The value
const p = Promise.resolve(x) // The context
const f = n =>
Promise.resolve(n * 2) // The function
const result = p.then(f) // The application
result.then(
r => console.log(r) // 40
)
}
如果這樣來看,是否可以把 .then()
變成 .flatMap()
那 Promise 到底算不算 Monad ? 嚴格來說並不是。
拿個例子來看
var p1 = () => Promise.resolve(1) // Promise
var f = x => x + 2 // Monad map?
var p2 = p1().then(f) // Prmoise
f 應該是 a => M(b) 的概念,但上面的 f 是 a => b 的概念,還是有些許的差異,不過就看我們怎去處理,在嚴格的一些數學轉換上,Promise 可能就會有些例外狀況。
Kleisli Composition Function
建一個 promise-lifting 的 compose function
const composeM = method => (...ms) => (
ms.reduce((f, g) => x => g(x)[method](f))
)
標準的 compose map
{
// The algebraic definition of function composition:
// (f ∘ g)(x) = f(g(x))
const compose = (f, g) => x => f(g(x))
const x = 20 // The value
const arr = [x] // The container
// Some functions to compose
const g = n => n + 1
const f = n => n * 2
// Proof that .map() accomplishes function composition.
// Chaining calls to map is function composition.
trace('map composes')([
arr.map(g).map(f),
arr.map(compose(f, g))
])
// => [42], [42]
}
改寫一下
const composeMap = (...ms) => (
ms.reduce((f, g) => x => g(x).map(f))
)
複製一下上面的東西來修改,其實這個很符合 api 的觀念,打 api 之後(async)取得一包值(a),再用這包值(a)去打別的 api aysnc 取得 b,大概的觀念就像 a => M(b),b => M(c),這個比 a => b => c 複雜多了一層。
{
const composePromises = (...ms) => (
ms.reduce((f, g) => x => g(x).then(f))
);
const label = 'Promise composition'
const g = n => Promise.resolve(n + 1)
const f = n => Promise.resolve(n * 2)
const h = composePromises(f, g)
h(20)
.then(trace(label))
// Promise composition: 42
}
再來過一層包裝
const composeM = method => (...ms) => (
ms.reduce((f,g) => x => g(x)[method](f))
)
重新定義一下
const composePromise = composeM('then')
const composeMap = composeM('map')
const composeFlatMap = composeM('flatMap')
資料來源