楔子
來試著說明何謂 monad 這其實是數學的一個名稱,但撇開那些艱深的道理,試試來用 Promise 的例子來說明看看。
Function Compose
最原始的 compose 的觀念就是把多個 function 做組合
const x = 20
const f = n => n * 2
const arr = Array.of(x)
const result = arr.map(f)
例如 echo
就是吃二個參數回傳一個 function
const echo = n => x => Array.from({length: n}).fill(x)
console.log(
[1,2,3].map( echo(3) )
)
例如 flatMap
就是吃二個參數回傳一個 function
const flatMap = (f, arr) => [].concat(...arr.map(f))
const echo = n => x => Array.from({length: n}).fill(x)
console.log(
flatMap( echo(3), [1,2,3])
)
公式鏈
- g: a => b
- f: b => c
- h: a => c
典型的 compose 例子,看圖就可以了解
公式鏈
- g: F(a) => F(b)
- f: F(b) => F(c)
- h: F(a) => F(c)
這是開始進入 functional programming 的範圍,把 F(a) 視為一個容器,包著一個值 a,而這個容器也可以用 compose function 的概念。
公式鏈
- f: a => M(b)
- g: ??? b => M(c)
- h: a. => M(c)
如果再使用成另一個 container (Monad)
拿一個實在的例子 Promise
常常會在 console 下看到 Promise<pending>
getUserById(id: String) => Promise(User)
hasPermision(User) => Promise(Boolean)
這是一個純粹用 value 的 compose 的例子,純粹使用 function 的 compose
const compose = (...fns) => x => fns.reduceRight((y, f) => f(y))
const trace = label => value => {
console.log(`${label}: ${value}`)
}
這是使用一個 Promise 的 map 但如果純粹使用會有噴 error
const label = 'API call composition'
// a => Promise(b)
const getUserById = id => id === 3 ? Promise.resolve({name: 'Kurt', role: 'Author'}) : undefined
// b => Promise(c)
const hasPermission = ({role}) => (
Promise.resolve(role === 'Author')
)
// Try to compose them. Warnning: this will fail
const authUser = compose(hasPermission, getUserById)
// Oops! Always false!
authUser(3).then(trace(label))
需要再進行一次 .then()
的轉化
const composeM = flatMap => (...ms) => (
ms.reduce((f, g) => x => g(x)[flatMap](f))
)
const composePromises = composeM('then')
const label = 'API call composition'
// a => Promise(b)
const getUserById = id => id === 3 ? Promise.resolve({name: 'Kurt', role: 'Author'}) : undefined
// b => Promise(c)
const hasPermission = ({role}) => (
Promise.resolve(role === 'Author')
)
// Try to compose them. Warnning: this works!
const authUser = composePromises(hasPermission, getUserById)
authUser(3).then(trace(label)) // true
資料來源