I am writing a program/pipeline using fp-ts
modules Either
and TaskEither
where the first step is to perform a TaskEither asynchronous operation that may possibly fail ("fetch document for ID"), and in a pipeline do synchronous processing that may also fail ("decode result").
function fetchBlob(id: string): TaskEither<Error, Body> { ... }
function decode(b: Body): Either<Error, MyDocumentT> { ... }
I am trying to write a function that hygienically passes the result from one to the other. Since there is an asynchronous call, this main function will return TaskEither<Error, MyDocumentT>
.
Seemingly no matter what I do, I end up with some variation of TaskEither<Error, Either<Error, MyDocumentT>>
, either explicitly when I do that:
function main(id: string): TaskEither<Error, MyDocumentT> {
return pipe(
id,
fetchBlob,
TE.chain((b) => decode(b)),
)
}
Or i circumvent it with this:
function main(id: string): TaskEither<Error, MyDocumentT> {
return pipe(
id,
fetchBlob,
TE.chain((b) => {
return pipe(
b,
decode,
TE.fromEither,
)
}),
)
}
This second example works but feels un-idiomatic somehow, this nested piping. It resembles the use of fromEither
in this related question -- there, the simplicity of the code relies on a one-line computation on the Either to avoid using a pipe like this (afaict). Is that really the recommended way? Is there a TE method for using TaskEither<E, A>
and Either<E, B>
such that the Either computations are inherently comingled with the TaskEithers? It seems like if I knew more of the keywords in the functional programming theory I would know just what to search for... It should be something like
const specialChain: <E, A, B>((a: A) => Either<E, B>) => TaskEither<E,A> => TaskEither<E, B>
Thank you!