在 fp-ts 中的 Either 上使用 getApplicativeValidation 后如何通过管道传递 ap 调用?
How to pipe ap calls after using getApplicativeValidation on an Either in fp-ts?
昨天我和一些同事试图让一个玩具示例在 fp-ts 中进行应用验证。我们最终通过手动将每个中间步骤存储在变量中并调用下一步来实现它。但是使用 fp-ts 的管道函数会更优雅。使用 Either 直接工作,但不会将多个 Left 值合并为一个(例如,连接带有字符串错误的数组)。
但是使用 pipe() 时,ap() 调用需要两个参数,但只得到一个。这里我们如何正确使用管道:
import * as E from "fp-ts/lib/Either";
import * as RA from "fp-ts/lib/ReadonlyArray";
import { pipe } from "fp-ts/lib/function";
export class CreditCard {
constructor(
public readonly number: string,
public readonly expiry: string,
public readonly cvv: string
) { }
}
export const validate = (
card: CreditCard
): E.Either<ReadonlyArray<string>, CreditCard> => {
const createCreditCard = (a: string) => (b: string) => (c: string) =>
new CreditCard(a, b, c);
const v1 = (s: string): E.Either<ReadonlyArray<string>, string> => {
return s !== "invalid" ? E.right(s) : E.left(RA.of("invalid number"));
};
const v2 = (s: string): E.Either<ReadonlyArray<string>, string> => {
return s !== "invalid" ? E.right(s) : E.left(RA.of("invalid expiry"));
};
const v3 = (s: string): E.Either<ReadonlyArray<string>, string> => {
return s !== "invalid" ? E.right(s) : E.left(RA.of("invalid cvv"));
};
const V = E.getApplicativeValidation((RA.getSemigroup<string>()));
// this does not work, because V.ap wants 2 arguments but only has 1?
// const fromPipe = pipe(
// V.of(createCreditCard),
// V.ap(v1(card.number)),
// V.ap(v2(card.expiry)),
// V.ap(v3(card.cvv))
// );
// return fromPipe;
// this works, but is ugly
const liftedFunction = V.of(createCreditCard);
const afterFirstValidation = V.ap(liftedFunction, v1(card.number));
const afterSecondValidation = V.ap(afterFirstValidation, v2(card.expiry));
const afterThirdValidation = V.ap(afterSecondValidation, v3(card.cvv));
return afterThirdValidation;
};
Either.getApplicativeValidation
returns Applicative2C
的实例,它具有 class 方法的非管道版本。目前,获取使用组合器(如 getApplicativeValidation
)计算的实例的可管道版本的方法是将实例从 pipeable.ts
模块传递给 pipeable
组合器。
因此将您的代码更改为:
const validation = E.getApplicativeValidation(RA.getSemigroup<string>())
const V = pipeable(validation)
const fromPipe = pipe(
validation.of(createCreditCard),
V.ap(v1(card.number)),
V.ap(v2(card.expiry)),
V.ap(v3(card.cvv))
)
您应该会发现它如您所愿。
但是,我相信 fp-ts
v3.x.x,类型 class 接口将默认更改为所有可管道化,因此对 pipeable
的需求将消失那么.
昨天我和一些同事试图让一个玩具示例在 fp-ts 中进行应用验证。我们最终通过手动将每个中间步骤存储在变量中并调用下一步来实现它。但是使用 fp-ts 的管道函数会更优雅。使用 Either 直接工作,但不会将多个 Left 值合并为一个(例如,连接带有字符串错误的数组)。 但是使用 pipe() 时,ap() 调用需要两个参数,但只得到一个。这里我们如何正确使用管道:
import * as E from "fp-ts/lib/Either";
import * as RA from "fp-ts/lib/ReadonlyArray";
import { pipe } from "fp-ts/lib/function";
export class CreditCard {
constructor(
public readonly number: string,
public readonly expiry: string,
public readonly cvv: string
) { }
}
export const validate = (
card: CreditCard
): E.Either<ReadonlyArray<string>, CreditCard> => {
const createCreditCard = (a: string) => (b: string) => (c: string) =>
new CreditCard(a, b, c);
const v1 = (s: string): E.Either<ReadonlyArray<string>, string> => {
return s !== "invalid" ? E.right(s) : E.left(RA.of("invalid number"));
};
const v2 = (s: string): E.Either<ReadonlyArray<string>, string> => {
return s !== "invalid" ? E.right(s) : E.left(RA.of("invalid expiry"));
};
const v3 = (s: string): E.Either<ReadonlyArray<string>, string> => {
return s !== "invalid" ? E.right(s) : E.left(RA.of("invalid cvv"));
};
const V = E.getApplicativeValidation((RA.getSemigroup<string>()));
// this does not work, because V.ap wants 2 arguments but only has 1?
// const fromPipe = pipe(
// V.of(createCreditCard),
// V.ap(v1(card.number)),
// V.ap(v2(card.expiry)),
// V.ap(v3(card.cvv))
// );
// return fromPipe;
// this works, but is ugly
const liftedFunction = V.of(createCreditCard);
const afterFirstValidation = V.ap(liftedFunction, v1(card.number));
const afterSecondValidation = V.ap(afterFirstValidation, v2(card.expiry));
const afterThirdValidation = V.ap(afterSecondValidation, v3(card.cvv));
return afterThirdValidation;
};
Either.getApplicativeValidation
returns Applicative2C
的实例,它具有 class 方法的非管道版本。目前,获取使用组合器(如 getApplicativeValidation
)计算的实例的可管道版本的方法是将实例从 pipeable.ts
模块传递给 pipeable
组合器。
因此将您的代码更改为:
const validation = E.getApplicativeValidation(RA.getSemigroup<string>())
const V = pipeable(validation)
const fromPipe = pipe(
validation.of(createCreditCard),
V.ap(v1(card.number)),
V.ap(v2(card.expiry)),
V.ap(v3(card.cvv))
)
您应该会发现它如您所愿。
但是,我相信 fp-ts
v3.x.x,类型 class 接口将默认更改为所有可管道化,因此对 pipeable
的需求将消失那么.