how can i avoid the pyramid of doom by using chains in fp-ts?

There is a solution to the problem, and it’s called “do notation”. It’s been available in fp-ts-contrib for a while, but it now also has a version baked into fp-ts itself using the bind function (which is defined on all monadic types). The basic idea is similar to what I was doing below – we bind computation results to a particular name, and track those names inside a “context” object as we go along. Here’s the code:

pipe(
  TE.of<Error, string>('one'),
  TE.bindTo('one'), // start with a simple struct {one: 'one'}
  TE.bind('two', op1), // the payload now contains `one`
  TE.bind('three', op2), // the payload now contains `one` and `two`
  TE.bind('four', op3), // the payload now contains `one` and `two` and `three`
  TE.map(x => x.four)  // we can discharge the payload at any time
)

Original Answer below

I’ve come up with a solution that I’m not very proud of, but I’m sharing it for possible feedback!

Firstly, define some helper functions:

function mapS<I, O>(f: (i: I) => O) {
  return <R extends { [k: string]: I }>(vals: R) =>
    Object.fromEntries(Object.entries(vals).map(([k, v]) => [k, f(v)])) as {
      [k in keyof R]: O;
    };
}
const TEofStruct = <R extends { [k: string]: any }>(x: R) =>
  mapS(TE.of)(x) as { [K in keyof R]: TE.TaskEither<unknown, R[K]> };

mapS allows me to apply a function to all values in an object (subquestion 1: is there a builtin function that would allow me to do this?). TEofStruct uses this function to turn a struct of values into a struct of TaskEithers for those values.

My basic idea is to accumulate the new value along with the previous values using TEofStruct and sequenceS. So far it looks like this:

pipe(
  TE.of({
    one: 'one',
  }),
  TE.chain((x) =>
    sequenceTE({
      two: op1(x),
      ...TEofStruct(x),
    })
  ),
  TE.chain((x) =>
    sequenceTE({
      three: op2(x),
      ...TEofStruct(x),
    })
  ),
  TE.chain((x) =>
    sequenceTE({
      four: op3(x),
      ...TEofStruct(x),
    })
  )
);

It feels like I could write some kind of helper function that combines sequenceTE with TEofStruct to reduce the boilerplate here, but I’m still not sure overall if this is the right approach, or if there is a more idiomatic pattern!

CLICK HERE to find out more related problems solutions.

Leave a Comment

Your email address will not be published.

Scroll to Top