昨天我和几个同事试图让一个玩具示例在 fp-ts 中进行应用验证。我们终于让它通过手动将每个中间步骤存储在一个变量中并调用下一步来工作。但是使用 fp-ts 的 pipe 函数会更加优雅。直接使用 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;
};