【问题标题】:How to combine the left sides of Either objects in fp-ts?如何在fp-ts中组合Either对象的左侧?
【发布时间】:2019-09-18 20:26:47
【问题描述】:

对于返回Either 的不同值,我有两个验证函数。如果其中一个具有left 值,我想抛出一个异常,如果两者都是right,则什么也不做。我以前从未使用过 fp-ts 并且无法弄清楚如何正确组合左侧结果。我目前的解决方案有效,但感觉我没有正确使用它。

import { Either, left, right, isLeft, getOrElse } from 'fp-ts/lib/Either';

function validateMonth( m: Month ): Either<Error, Month> {
   return m.isInRange() ? right(m) : left(new Error('Month must be in range!'));
}

function validateYear( y: Year ): Either<Error, Year> {
   return year.isBefore(2038) ? right(y) : left(new Error('Year must be before 2038!'));
}

function throwingValidator(m: Month, y: Year): void {
 // todo: Refactor to get rid of intermediate variables,
 // combining results of validateMonth and validateYear into a type
 // of Either<Error, Unit>
 const monthResult = validateMonth( month );
 const yearResult = validateYear( year );
 const throwOnError = (e: Error) => { throw e; };
 if ( isLeft( monthResult ) ) { getOrElse(throwOnError)(monthResult); }
 if ( isLeft( yearResult ) ) { getOrElse(throwOnError)(yearResult); }
}

我已阅读https://dev.to/gcanti/getting-started-with-fp-ts-either-vs-validation-5eja 的介绍,但该代码与我想要的完全相反:我不关心验证后的输入值,只想返回发生的第一个错误。

【问题讨论】:

  • getOrElse 用于获取结果值,但您没有对它的返回值做任何事情 - 为什么还要调用它?尤其是为什么只在您已经确定它是Left 值时才调用它?也就是说,抛出异常在函数式编程中并不是一个真正的好习惯,所以自然有点难。
  • 如果不关心返回值,Either 不是返回类型的正确选择。或者至少,不是Either&lt;Error, Result&gt; - 充其量你会做Either&lt;Error, Unit&gt;。 (在 TypeScript 中,Unitvoidundefinednull)。
  • 我滥用getOrElse 来调用错误情况的函数。但是您对异常的看法是正确的-我真正想做的是捕获Error 对象并将其以被拒绝的承诺返回。当所有值都正确时,我想返回一个具有特定数据结构的 Promise。
  • 至少你不应该同时需要isLeftgetOrElse - 一个就足够了。
  • 人们还可以查看这个示例:codesandbox.io/s/…

标签: typescript functional-programming either fp-ts


【解决方案1】:

忽略throwingValidator 并笼统地抛出(这违背了首先使用fp-ts 的目的)并仅关注此特定请求:

重构去掉中间变量,将 validateMonth 和 validateYear 的结果组合成一个 Either

您可能正在寻找:

const monthAndYearResult: Either<
  Error,
  { month: Month, year: Year }
> = sequenceS(either)({
  month: validateMonth(month),
  year: validateYear(year)
})

"sequence"ing 通常需要Traversable 的实例(在这种情况下是结构{ year, month })和Applicative 的实例(在这种情况下是either),语义是聚合的不同的独立计算在一起。

如果您明确想要忽略结果,通常会提供_-suffix 替代方案来完成此操作,但目前fp-ts v2 中还没有。

要获得Either&lt;Error, void&gt;,您可以求助于:

const result = pipe(
  sequenceS(E.either)({ month: validateMonth(month), year: validateYear(year) }),
  E.map(constVoid)
)

注意sequenceS 只是可能的选项之一,您可以使用sequenceTarray.sequence 来获得类似的结果,例如:

pipe(
  sequenceT(either)([validateMonth(month), validateYear(year)]),
  E.map(constVoid)
)

【讨论】:

    【解决方案2】:

    您可能正在寻找类似的东西

    const toPromise = fold(e => Promise.reject(e), r => Promise.resolve(r));
    
    Promise.all([
        toPromise(validateMonth(month)),
        toPromise(validateYear(year)),
    ]).then(([validMonth, validYear]) => {
        return …
    });
    

    或者更实用的方式

    toPromise(ap(ap(of(validMonth => validYear => {
        return …
    }), validateMonth(month)), validateYear(year)))
    

    您也可以使用array.sequencetoPromise 之后的Promise.all

    【讨论】:

    • @bob 出于某种原因,我认为map 确实有错误的(翻转)签名,我完全错过了liftA2
    • @Bergi 为什么要在这里介绍Promise
    • @GiovanniGonzaga OP 写道(在评论中)“当所有值都正确时,我想返回一个具有特定数据结构的 Promise”。使用被拒绝的 Promise 作为返回值比抛出更容易。
    • 我明白了,我很困惑,因为 Promise/async 在原始问题中没有提到。重新“作为返回值被拒绝的承诺比抛出更容易”,等效的 fp-ts 编码将是 TaskEither 这是 () =&gt; Promise&lt;Either&lt;L, A&gt;&gt; (懒惰,因为承诺急切地打破参照透明度)
    猜你喜欢
    • 1970-01-01
    • 2020-08-16
    • 1970-01-01
    • 1970-01-01
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 2023-03-10
    • 2021-07-17
    相关资源
    最近更新 更多