1

I'm relatively new to functional programming and very new to fp-ts, so I'm having a struggle wrapping my head around the util functions provided. I'm currently trying to figure out how to handle TaskEithers as fallbacks in an array.

I have a function to fetch data for some id, that returns Error or Success:

declare function getData(id: number): TaskEither<Error, Success>

What I want is a function that will iterate through some array of ids (ex. [1, 2, 3, 4]), requesting data for each one. It should stop at the first TaskEither that succeeds and return Right<Success>. If all of the TaskEithers fail, it should collect their errors into a Left<Error[]>.

import { map } from 'fp-ts/lib/Array';

const program: TaskEither<Error[], Success>
  = pipe(
    [1, 2, 3, 4],
    map(getData),
    /*
     * Now I have a TaskEither<Error, Success>[]
     * What comes next?
     */
  );

I tried something similar, but there are some obvious problems (noted below):

import { map, sequence } from 'fp-ts/lib/Array';
import { map as mapTaskEither } from 'fp-ts/lib/TaskEither'

const program: TaskEither<Error, Success>
  = pipe(
    [1, 2, 3, 4],
    map(getData),
    sequence(taskEither), // Now this gets to a TaskEither<Error, Success[]>
    mapTaskEither(successes => successes[0])
  );

Problems with this approach

  1. It runs getData on all of the IDs, without short-circuiting on the first success
  2. It errors if any of the getDatas errors. So if getData(4) errors, the overall program will error even if getData(1) succeeded
  3. It does not collect the errors into an array of Error[]
bigpopakap
  • 381
  • 5
  • 13
  • I found a very good answer to a similar question, but I'm having an extremely hard time wrapping my head around al the layers of abstractions in functional programming. https://stackoverflow.com/questions/61133166/managing-array-of-monads-in-fp-ts-and-functional-programming – bigpopakap Jul 06 '20 at 22:37
  • 1
    I'll let someone who is more familiar with fp-ts in particular give a full answer, but I believe you'll have to implement your own function (or your own Traversable instance for TaskEither) if you want to retain the Left=Error convention. If you make Right=Error, I think your code meets your requirements. – Karl Bielefeldt Jul 07 '20 at 03:24
  • Oh that's a great insight. It gives me the idea to just do something like: ..., map(swap), sequence(taskEither), swap ... I'll try putting this into its own function so it can just be consolidated for readability. I'll post an answer to this question if it works. Great idea! – bigpopakap Jul 07 '20 at 23:44

1 Answers1

1

Thanks to this insightful comment, I've come up a solution.

It helps with readability to give this util function a name:

import { TaskEither, taskEither, swap } from 'fp-ts/lib/TaskEither';
import { sequence, map } from 'fp-ts/lib/Array';
import { pipe } from 'fp-ts/lib/function';

const firstRightOrLefts: <A, B>(taskEithers: TaskEither<A, B>[]) => TaskEither<A[], B>
    = taskEithers => pipe(
        taskEithers,
        map(swap),
        sequence(taskEither),
        swap,
    );

And then it can be used like this:

import { map } from 'fp-ts/lib/Array';

const program: TaskEither<Error, Success>
  = pipe(
    [1, 2, 3, 4],
    map(getData),
    getFirstRightOrLefts, // <-- Woohoo!
  );
bigpopakap
  • 381
  • 5
  • 13