有没有一种更干净的方式来表达TypeScript中的许多嵌套映射函数



给定下面的代码,是否有更干净的方法来表达TypeScript中的许多嵌套映射函数?我喜欢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库来处理诸如"非此即彼"之类的类型。

有人有任何技巧、建议和/或库可以帮助清理我的嵌套映射函数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表示法的一个端口,所以所有的功劳都归于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))

游乐场链接

最新更新