【问题标题】:How to fold two different types? FP-TS如何折叠两种不同的类型? FP-TS
【发布时间】:2021-05-26 02:59:45
【问题描述】:

我正在使用 FP-TS 学习 FP,但遇到了障碍:

我的存储库中有以下功能:

// this is the repository
export const findBook = (id: string) => TaskEither<Error, Option<ParsedBook>> 

这部分很简单,对我来说很有意义。问题是当我尝试从控制器调用它时:

// this is the controller
export const getBook = (req: unknown) => Task<string | ParsedBook>

无论如何,这是我的getBook 以及我正在尝试做的事情:

const getBook: (req: unknown) => T.Task<string | ParsedBook> = flow(
  getBookByIdRequestV.decode, // returns Either<Errors, GetBookByIdRequest>
  E.fold(
    () => T.of('Bad request!'),
    flow(
      prop('params'),
      prop('id'),
      findBook, // returns TaskEither<Error, Option<ParsedBook>>
      TE.fold(
        () => T.of('Internal error.'),
        O.fold(
          () => T.of('Book not found.'),
          (book) => T.of(book) // this line results in an error
        )
      )
    )
  )
)

问题是上面的代码给了我一个错误:

Type 'ParsedBook' is not assignable to type 'string'

我认为问题在于E.fold 期望onLeftonRight 的结果返回相同的类型:

fold<E, A, B>(onLeft: (e: E) => B, onRight: (a: A) => B): (ma: Either<E, A>) => B

但是,它可能不仅返回 Task&lt;string&gt;,还可能返回 Task&lt;ParsedBook&gt;

我尝试使用foldW,它扩大了类型,但同样的错误。

我真的不知道该怎么办;我觉得我在代码中建模类型的方式很糟糕?

如果有帮助,这里是 Codesandbox:https://codesandbox.io/s/tender-chandrasekhar-5p2xm?file=/src/index.ts

谢谢!

【问题讨论】:

    标签: typescript functional-programming fp-ts


    【解决方案1】:

    您在尝试使用foldW 时遇到的错误是因为Task&lt;string&gt; | Task&lt;ParsedBook&gt; 不能分配给Task&lt;string | ParsedBook&gt;

    您可以将T.mapE.foldW 一起使用,而不是使用TE.fold 解开TaskEither,然后使用T.of 重新包装任务中的值:

    import { identity } from "fp-ts/function";
    
    T.map(E.foldW(
      () => "Internal error.",
      O.foldW(() => "Book not found.", identity)
    ))
    

    事实上,使用O.getOrElseW 有一种更简单的方法:

    T.map(E.foldW(
      () => "Internal error.",
      O.getOrElseW(() => "Book not found.")
    ))
    

    完整代码:

    const getBook: (req: unknown) => T.Task<string | ParsedBook> = flow(
      getBookByIdRequestV.decode,
      E.fold(
        () => T.of("Bad request!"),
        flow(
          prop("params"),
          prop("id"),
          findBook,
          T.map(E.foldW(
            () => "Internal error.",
            O.getOrElseW(() => "Book not found.")
          ))
        )
      )
    )
    

    【讨论】:

      【解决方案2】:

      很好,我正在尝试自己了解更多关于 FP 的信息。

      我不确定我是否理解getBook 应该做什么。但我会将其保留为TaskEither,因为它应该代表失败的可能性。

      在使用 Functor/Monad 时,我认为它们就像一个盒子。我想把我的价值观保存在盒子里。如果您查找fold,您会发现它列在析构函数下,那是因为它会破坏您的盒子。所以避免fold,如果必须的话,可以在计算结束时这样做。

      但是,如果我们的价值观被困在一个盒子里,我们该怎么做呢? map 是我们在这里的第一个解决方案,因为我们可以采用一个作用于值的函数现在作用于框。但它使我们的价值观保持不变。

      我们还可以将 Box 替换为 TE.fromEitherTE.fromOption,这样可以将我们的值保存在它的盒子内。

      最后我们可以chain 只保留一个盒子。每次map 会给我们一个盒子中的盒子,我们可以将其更改为chain。但是盒子的类型必须对齐,否则你必须先转换它。

      考虑到这些,我会这样写getBook

      const getBook2: (req: unknown) => TE.TaskEither<string, ParsedBook> = flow(
        getBookByIdRequestV.decode,
        E.mapLeft(() => 'Bad request!'),
        E.map(({ params }) => params.id),
        TE.fromEither,
        TE.chain(flow(
          findBook,
          TE.mapLeft(() => 'internal error.')
        )),
        TE.chain(TE.fromOption(() => 'Book not found.'))
      )
      

      希望这会有所帮助,我仍在学习自己。如果有更好的方法,请告诉我。

      【讨论】:

        猜你喜欢
        • 2019-07-24
        • 1970-01-01
        • 2011-08-23
        • 2016-05-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-17
        • 1970-01-01
        相关资源
        最近更新 更多