Search code examples
typescriptfunctional-programmingmonadseitherfp-ts

fp-ts TaskEither pipeline composed with Either pipelines


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!


Solution

  • I haven't worked with fp-ts but I can try to help you with functional programming. TaskEither and Either are Moands, which means they follow the same rules. What you can do is to chain operations like you are trying to do, but the problem is that you can't operate different ones without doing some transformation. For that, you will need to use Monad Transformers (I couldn't find any good article about this concept, hope this one helps). You also have a syntax sugar known as do notation that can help you to have a more readable code when you are chaining operations.

    That being said, what you need to do is to transform the Either to a TaskEither as you did with fromEither. If you want to avoid that nesting you have to make it "more readable / easier to read" (if you are not used to read code in that way, it take some time to get used to), you can use the do notation

    If I'm not wrong, maybe chainEitherK could also help. The signature looks very similar to the the function specialChain that you put as example.

    Based on my quick read and what I understood, your code could be something like

    function main(id: string): TaskEither<Error, MyDocumentT> {
        return pipe(
          id,
          fetchBlob,
          TE.fromEither((b) => decode(d)) // not sure if it can also be written like TE.fromEither(decode)
        )
    }
    

    I think the following series of articles can help you in the future