什么时候应该使用 E.altW 而不是 E.orElse?

When should I use E.altW as opposed to E.orElse?

提问人:Danielo515 提问时间:10/30/2023 更新时间:11/1/2023 访问量:19

问:

我试图理解 E.altW 和 E.orElse 之间的差异,但两者似乎都是一样的。例如,我可以构造一个 Semigroup,它使用其中任何一个连接解析函数:

import * as E from 'fp-ts/Either';
import * as A from 'fp-ts/Array';
import { pipe } from 'fp-ts/function';
import * as S from 'fp-ts/Semigroup';

const parseString = (u: unknown): E.Either<string, string> =>
  typeof u === 'string' ? E.right(u) : E.left('not a string');

const parseNumber = (u: unknown): E.Either<string, number> =>
  typeof u === 'number' ? E.right(u) : E.left('not a number');

const ParseSemigroup: S.Semigroup<
  (u: unknown) => E.Either<string, string | number>
> = {
  concat(a, b) {
    return (input) =>
      pipe(
        a(input),
        E.altW(() => b(input))
      );
  },
};

const ParseSemigroup2: S.Semigroup<
  (u: unknown) => E.Either<string, number | string>
> = {
  concat(a, b) {
    return (input) =>
      pipe(
        a(input),
        E.orElse(() => b(input))
      );
  },
};

const manyParsers = S.concatAll(ParseSemigroup)(parseNumber)([parseString]);
console.log(manyParsers('99'));

const manyParsers2 = S.concatAll(ParseSemigroup2)(parseNumber)([parseString]);
console.log(manyParsers('99'));

正如你所看到的,结果是完全相同的。 什么时候我应该使用一个而不是另一个?或者每个的具体用例是什么?

FP-TS系列

评论


答:

1赞 Lauren Yim 11/1/2023 #1

首先,需要注意的一点是,它与 和 和 略有不同。后缀代表“加宽”,这意味着您可以执行以下操作:altWaltonElseWonElseW

const result: Either<never, string | number> = pipe(
  E.right(''),
  E.altW(() => E.right(0))
)

其中生成的 Union 类型在其内部。您可以在 fp-ts 常见问题解答中阅读更多相关信息。Either


以下是 和 的类型及其对应项:altonElseW

declare const     alt: <E , A    >(that:   ()      => Either<E , A>) =>        (fa: Either<E , A>) => Either<E , A    >
declare const  orElse: <E1, A, E2>(onLeft: (e: E1) => Either<E2, A>) =>        (ma: Either<E1, A>) => Either<E2, A    >
declare const    altW: <E2, B    >(that:   ()      => Either<E2, B>) => <E1, A>(fa: Either<E1, A>) => Either<E2, B | A>
declare const orElseW: <E1, E2, B>(onLeft: (e: E1) => Either<E2, B>) => <    A>(ma: Either<E1, A>) => Either<E2, B | A>

因为 TypeScript 类型看起来很混乱,所以这里是同样的事情,但语法类似于 Haskell:

alt     :: (() -> Either e  a) -> Either e  a -> Either e  a
orElse  :: (e1 -> Either e2 a) -> Either e1 a -> Either e2 a
altW    :: (() -> Either e2 b) -> Either e1 a -> Either e2 (b | a)
orElseW :: (e1 -> Either e2 b) -> Either e1 a -> Either e2 (b | a)

正如你所希望看到的,和之间的主要区别在于传递给的函数接受错误。在你传递给的函数忽略/不使用错误的情况下,并且是等效的。在这种情况下,是这里使用更合适的函数。altorElseorElseE1onElsealtonElsealt

如果要返回具有不同错误和/或成功类型的新错误,则应使用 /。EitheraltWorElseW

所以,总而言之:

  • 您是否需要使用第一个错误来返回第二个错误?
    • 是:使用orElse/orElseW
    • 否:使用alt/altW
  • 第二个与第一个有不同的类型吗?
    • 是:使用orElseW/altW
    • 否:使用orElse/alt

旁注:备选方案

您可能已经注意到,在 和 之间还有另一个区别:在 中,函数返回的 either 可以具有不同的错误类型。altorElseorElseE2

这是因为该函数来自 Alt 类型类:alt

interface Alt<F> extends Functor<F> {
  readonly alt: <A>(fa: HKT<F, A>, that: LazyArg<HKT<F, A>>) => HKT<F, A>
}

在 的情况下,你可以想到 like ,所以 和 中必须相同。EitherHKT<F, A>Either<E, A>Efathat