typescript - 有没有一种更简洁的方法可以在 TypeScript 中表达许多嵌套的地图函数?
问题描述
给定下面的代码,有没有更简洁的方法可以在 TypeScript 中表达许多嵌套的 map 函数?我喜欢这个用例的 Scala “理解”,但我在 TypeScript 中找不到等价物。我觉得我在这里遗漏了一些非常明显的东西。
由于验证原因,我有几个对象可能无法实例化,因此返回类型都是Either<string, T>
. 例如:
const userId: Either<string, UserId> = UserId.create('1234')
当组合由许多类似上述语句组成的对象时,看起来很粗糙。为了便于阅读,示例中的所有变量都已替换为字符串。
在 TypeScript 中,这就是我正在做的事情。有没有更简洁的方式来表达这一点而不会丢失我的类型?
const userSettings: Either<string, UserSettings> = UserId.create('1234').chain(userId => {
return Email.create('hello@world.com').chain(email => {
return Active.create(183).chain(active => {
return Role.create('admin').map(role => {
return UserSettings(userId, email, active, role)
})
})
})
})
在 Scala 中,我会这样表达上面的代码:
for {
userId <- UserId.create('1234')
email <- Email.create('hello@world.com')
active <- Active.create(183)
role <- Role.create('admin')
} yield UserSettings(userId, email, active, role)
我正在将Purify库用于诸如 Either 之类的类型。
有没有人有任何提示、建议和/或库可以帮助清理我的嵌套地图函数 TypeScript 混乱?
解决方案
你可以使用这样的东西:
const userSettings = Right({})
.chain(acc => UserId.create('1234').map(userId => ({...acc, userId})))
.chain(acc => Email.create('hello@world.com').map(email => ({...acc, email})))
.chain(acc => Active.create(183).map(active => ({...acc, active})))
.chain(acc => Role.create('admin').map(role => ({...acc, role})))
.map(({userId, email, active, role}) => UserSettings(userId, email, active, role))
您还可以定义一个辅助函数:
// This implementation works for all functors, but the types only work for
// Either due to TypeScript's lack of HKTs
const bind =
<N extends string, A extends object, L, R>(
name: Exclude<N, keyof A>,
f: (acc: A) => Either<L, R>
) =>
(acc: A): Either<L, A & Record<N, R>> =>
f(acc).map(r => ({...acc, [name]: r} as A & Record<N, R>))
const userSettings: Either<string, UserSettings> = Right({})
.chain(bind('userId', () => UserId.create('1234')))
.chain(bind('email', () => Email.create('hello@world.com')))
.chain(bind('active', () => Active.create(183)))
.chain(bind('role', () => Role.create('admin')))
.map(({userId, email, active, role}) => UserSettings(userId, email, active, role))
拥有bind
一个函数允许这样的事情:
Right({})
.chain(bind('a', () => Right(1)))
// The value of b depends on a
.chain(bind('b', ({a}) => Right(a + 1)))
// a is 1 and b is 2
.map(({a, b}) => `a is ${a} and b is ${b}`)
这几乎是fp-ts 实现 do notation 的一个端口,因此所有功劳都归功于 Giulio Canti 和 fp-ts 的贡献者。
如果你写() =>
了很多,你可以使用另一个助手:
// This could definitely be named better
const bind_ = <N extends string, A extends object, L, R>(
name: Exclude<N, keyof A>,
either: Either<L, R>
): ((acc: A) => Either<L, A & Record<N, R>>) => bind(name, () => either)
const userSettings: Either<string, UserSettings> = Right({})
.chain(bind_('userId', UserId.create('1234')))
.chain(bind_('email', Email.create('hello@world.com')))
.chain(bind_('active', Active.create(183)))
.chain(bind_('role', Role.create('admin')))
.map(({userId, email, active, role}) => UserSettings(userId, email, active, role))
推荐阅读
- r - 根据R中的两列分配有序数
- elasticsearch - Elasticsearch,如何计算正态分布的累积概率?
- json - 将来自 ASX 的 JSON 数据解析为交易所交易产品的 Google 表格 - 而不是公司
- python - 如何使用 itertools 改进我的慢速嵌套 for 循环?
- reactjs - React useEffect deps 被调用
- aws-lambda - 将无服务器框架与 AWS 结合使用,Sequelize 的自动生成的表别名因环境而异(离线与 Lambda)
- python - how do I iterate only once per function call
- macos - AppleScript 仅在我的 Mac 的 1 个帐户上告诉应用程序“系统事件”非常慢
- python - changing the dtype of array
- pine-script - Pine Script Strategy - How to set a simultaneous STOP exit for 100% and TAKE exit for only 50%?