如何折叠两种不同的类型? 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
期望 onLeft
和 onRight
的结果与 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.fromEither
或 TE.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.map
和 E.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.")
))
)
)
)
我正在使用 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
期望 onLeft
和 onRight
的结果与 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.fromEither
或 TE.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.map
和 E.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.")
))
)
)
)