1

昨天我和几个同事试图让一个玩具示例在 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;
};
4

1 回答 1

3

Either.getApplicativeValidation返回一个实例,Applicative2C该实例具有类方法的不可管道版本。目前,您获得使用组合器(如 )计算的实例的可管道版本的方式是将实例从模块getApplicativeValidation传递给pipeable组合器。pipeable.ts

因此,将您的代码更改为:

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-tsv3.xx 中,类型类接口默认情况下将更改为全部可管道,因此对的需求pipeable将消失。

于 2021-06-11T15:52:07.420 回答