如何折叠两种不同的类型? FP-TS

How to fold two different types? FP-TS

我正在使用 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 的结果与 return 相同类型:

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

然而,它不仅可能 return 一个 Task<string>,而且可能 Task<ParsedBook>

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

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

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

谢谢!

很好,我正在努力学习更多关于 FP 的知识。

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

在使用 Functors/Monads 时,我认为它们就像一个盒子。我想把我的价值观保存在盒子里。如果你查找 fold 你会发现它列在析构函数下,那是因为它有点破坏你的盒子。因此,请避免 fold,如果必须的话,可以在计算结束时执行。

但是,如果我们的值卡在一个盒子里,我们该如何处理它们呢? map 是我们的第一个解决方案,因为我们可以将作用于值​​的函数现在作用于框。但它使我们的价值观保持不变。

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

最后我们可以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.'))
)

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

您在尝试使用 foldW 时遇到的错误是因为 Task<string> | Task<ParsedBook> 无法分配给 Task<string | ParsedBook>

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

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.")
      ))
    )
  )
)