Mar*_*oni 2 javascript monads functional-programming futuretask either
我很难理解monad变换器,部分原因是大多数示例和解释都使用Haskell.
任何人都可以举一个例子来创建一个变换器来合并Javascript中的Future和Either monad以及如何使用它.
如果你可以使用ramda-fantasy这些monad 的实现,那就更好了.
Tha*_*you 15
规则第一
首先,我们有自然转型法
F的a,具有功能映射f,产率F的b话,自然转化,产生一些函子G的b.F的a,自然转化产生一些函子G的a,然后用一些函数映射f,产率G的b选择任一路径(第一映射,变换第二,或第一变换,映射第二)会导致相同的最终结果,G的b.
nt(x.map(f)) == nt(x).map(f)
Run Code Online (Sandbox Code Playgroud)
变得真实
好的,现在让我们来做一个实际的例子.我将逐位解释代码,然后我将在最后有一个完整的可运行示例.
首先我们将实现Either(使用Left和Right)
const Left = x => ({
map: f => Left(x),
fold: (f,_) => f(x)
})
const Right = x => ({
map: f => Right(f(x)),
fold: (_,f) => f(x),
})
Run Code Online (Sandbox Code Playgroud)
然后我们将实施 Task
const Task = fork => ({
fork,
// "chain" could be called "bind" or "flatMap", name doesn't matter
chain: f =>
Task((reject, resolve) =>
fork(reject,
x => f(x).fork(reject, resolve)))
})
Task.of = x => Task((reject, resolve) => resolve(x))
Task.rejected = x => Task((reject, resolve) => reject(x))
Run Code Online (Sandbox Code Playgroud)
现在让我们开始定义一些理论程序.我们将拥有一个用户数据库,每个用户都拥有一个bff(永远是最好的朋友).我们还将定义一个简单的Db.find函数,该函数返回在数据库中查找用户的任务.这类似于任何返回Promise的数据库库.
// fake database
const data = {
"1": {id: 1, name: 'bob', bff: 2},
"2": {id: 2, name: 'alice', bff: 1}
}
// fake db api
const Db = {
find: id =>
Task((reject, resolve) =>
resolve((id in data) ? Right(data[id]) : Left('not found')))
}
Run Code Online (Sandbox Code Playgroud)
好的,所以有一点点扭曲.我们的Db.find函数返回Task的Either(Left或Right).这主要是出于演示目的,但也可以说是一种好的做法.即,我们可能不认为用户未找到的场景是错误,因此我们不想要reject任务 - 相反,我们稍后通过解析 a Left来优雅地处理它'not found'.我们可能会reject在出现不同的错误时使用,例如无法连接到数据库或其他东西.
制定目标
我们程序的目标是获取给定的用户ID,并查找该用户的bff.
我们雄心勃勃,但天真,所以我们首先尝试这样的事情
const main = id =>
Db.find(1) // Task(Right(User))
.map(either => // Right(User)
either.map(user => // User
Db.find(user.bff))) // Right(Task(Right(user)))
Run Code Online (Sandbox Code Playgroud)
Yeck!a Task(Right(Task(Right(User))))...这很快失控了.这将是一场彻头彻尾的噩梦......
自然变革
这是我们的第一次自然转变eitherToTask:
const eitherToTask = e =>
e.fold(Task.rejected, Task.of)
// eitherToTask(Left(x)) == Task.rejected(x)
// eitherToTask(Right(x)) == Task.of(x)
Run Code Online (Sandbox Code Playgroud)
让我们看看当我们chain对Db.find结果进行转换时会发生什么
const main = id =>
Db.find(id) // Task(Right(User))
.chain(eitherToTask) // ???
...Run Code Online (Sandbox Code Playgroud)
那是什么????好的,Task#chain期望你的函数返回a Task,然后它会搜索当前的Task,以及新返回的Task.所以在这种情况下,我们去:
// Db.find // eitherToTask // chain
Task(Right(User)) -> Task(Task(User)) -> Task(User)
Run Code Online (Sandbox Code Playgroud)
哇.这已经是一个巨大的进步,因为当我们进行计算时,它会使我们的数据更加平坦.我们继续吧 ...
const main = id =>
Db.find(id) // Task(Right(User))
.chain(eitherToTask) // Task(User)
.chain(user => Db.find(user.bff)) // ???
...Run Code Online (Sandbox Code Playgroud)
那么???这一步又是什么呢?我们知道Db.find回报Task(Right(User)但是我们chain正在进行,所以我们知道我们将至少两次挤压Task.这意味着我们去:
// Task of Db.find // chain
Task(Task(Right(User))) -> Task(Right(User))
Run Code Online (Sandbox Code Playgroud)
看看那个,我们还有另一个Task(Right(User))我们已经知道如何压扁的东西.eitherToTask!
const main = id =>
Db.find(id) // Task(Right(User))
.chain(eitherToTask) // Task(User)
.chain(user => Db.find(user.bff)) // Task(Right(User))
.chain(eitherToTask) // Task(User) !!!Run Code Online (Sandbox Code Playgroud)
热土豆!好的,那我们如何处理呢?好吧main拿一个Int并返回一个Task(User),所以......
// main :: Int -> Task(User)
main(1).fork(console.error, console.log)
Run Code Online (Sandbox Code Playgroud)
这真的很简单.如果Db.find解析一个Right,它将被转换为Task.of(一个已解析的Task),这意味着结果将转到console.log- 否则,如果Db.find解析一个Left,它将被转换为一个Task.rejected(被拒绝的Task),这意味着结果将转到console.error
可运行的代码
// Either
const Left = x => ({
map: f => Left(x),
fold: (f,_) => f(x)
})
const Right = x => ({
map: f => Right(f(x)),
fold: (_,f) => f(x),
})
// Task
const Task = fork => ({
fork,
chain: f =>
Task((reject, resolve) =>
fork(reject,
x => f(x).fork(reject, resolve)))
})
Task.of = x => Task((reject, resolve) => resolve(x))
Task.rejected = x => Task((reject, resolve) => reject(x))
// natural transformation
const eitherToTask = e =>
e.fold(Task.rejected, Task.of)
// fake database
const data = {
"1": {id: 1, name: 'bob', bff: 2},
"2": {id: 2, name: 'alice', bff: 1}
}
// fake db api
const Db = {
find: id =>
Task((reject, resolve) =>
resolve((id in data) ? Right(data[id]) : Left('not found')))
}
// your program
const main = id =>
Db.find(id)
.chain(eitherToTask)
.chain(user => Db.find(user.bff))
.chain(eitherToTask)
// bob's bff
main(1).fork(console.error, console.log)
// alice's bff
main(2).fork(console.error, console.log)
// unknown user's bff
main(3).fork(console.error, console.log)Run Code Online (Sandbox Code Playgroud)
归因
我几乎完全答应了Brian Lonsdorf(@drboolean).他有一个关于Egghead的精彩系列,名为Frisby教授,介绍可组合功能JavaScript.很巧合的是,你的问题中的例子(转换Future和Either)与他的视频中使用的示例相同,并且在我的答案中使用此代码.
关于自然变换的两个是
任务的替代实施
Task#chain 有一点神奇的事情并没有立即显现出来
task.chain(f) == task.map(f).join()
Run Code Online (Sandbox Code Playgroud)
我提到这是一个旁注,因为它对于考虑上面的Either到Task的自然转换并不是特别重要.Task#chain足以进行演示,但是如果你真的想把它拆开来看看一切是如何工作的,那么它可能会感觉有点无法接近.
下面,我推导chain使用map和join.我会在下面放一些应该有帮助的类型注释
const Task = fork => ({
fork,
// map :: Task a => (a -> b) -> Task b
map (f) {
return Task((reject, resolve) =>
fork(reject, x => resolve(f(x))))
},
// join :: Task (Task a) => () -> Task a
join () {
return Task((reject, resolve) =>
fork(reject,
task => task.fork(reject, resolve)))
},
// chain :: Task a => (a -> Task b) -> Task b
chain (f) {
return this.map(f).join()
}
})
// these stay the same
Task.of = x => Task((reject, resolve) => resolve(x))
Task.rejected = x => Task((reject, resolve) => reject(x))
Run Code Online (Sandbox Code Playgroud)
您可以在上面的示例中使用这个新任务替换旧任务的定义,并且所有内容仍然可以使用相同的^ _ ^
跟着原住民 Promise
ES6附带Promises,其功能与我们实施的任务非常相似.当然有很多不同之处,但是就本演示而言,使用Promise而不是Task将导致代码几乎与原始示例相同
主要区别是:
fork函数参数按顺序排序(reject, resolve)- Promise执行程序函数参数按(resolve, reject)顺序排序(反向顺序)promise.then而不是task.chainPromise.rejected并且Promise.resolve不能被称为第一类 - 每个需要被绑定的上下文Promise- 例如x => Promise.resolve(x)或Promise.resolve.bind(Promise)代替Promise.resolve(相同Promise.reject)// Either
const Left = x => ({
map: f => Left(x),
fold: (f,_) => f(x)
})
const Right = x => ({
map: f => Right(f(x)),
fold: (_,f) => f(x),
})
// natural transformation
const eitherToPromise = e =>
e.fold(x => Promise.reject(x),
x => Promise.resolve(x))
// fake database
const data = {
"1": {id: 1, name: 'bob', bff: 2},
"2": {id: 2, name: 'alice', bff: 1}
}
// fake db api
const Db = {
find: id =>
new Promise((resolve, reject) =>
resolve((id in data) ? Right(data[id]) : Left('not found')))
}
// your program
const main = id =>
Db.find(id)
.then(eitherToPromise)
.then(user => Db.find(user.bff))
.then(eitherToPromise)
// bob's bff
main(1).then(console.log, console.error)
// alice's bff
main(2).then(console.log, console.error)
// unknown user's bff
main(3).then(console.log, console.error)Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1052 次 |
| 最近记录: |