2

I am working on a project that using fp-ts

I have 2 TaskEither object like TaskEither<ErrorA, A>, TaskEither<ErrorB, B>

I wanted to merging this objects contents and create new TaskEither<ErrorA, A>,
Example A object = {a: 123, b: 456, c: 0}

Example B object = {c: 789}

I wanted to create newObject: TaskEither<ErrorA, A>

and If everything goes well, expecting value should be {a: 123, b: 456, c: 789 }

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 1
    Have you tried using `TaskEither.chain`? Similar issue here: https://stackoverflow.com/questions/61559808/chain-fp-ts-taskeither-with-either-in-right – Cameron Nov 30 '21 at 19:44

3 Answers3

3

I would recommend to use the Do-Notation in this case. Here an example.

import { pipe } from 'fp-ts/function'
import * as TE from 'fp-ts/TaskEither'

interface A {
  a: number
  b: number
  c: number
}

interface B {
  c: number
}

const a = TE.right<Error, A>({ a: 123, b: 456, c: 0 })
const b = TE.right<Error, B>({ c: 789 })

const c: TE.TaskEither<Error, A> = pipe(
  TE.Do,
  TE.apS('a', a),
  TE.apS('b', b),
  TE.map(({ a, b }) => ({ ...a, ...b }))
)

If the error types are not the same you should consider to wrap your errors in a union type. There is a longer thread about that in the fp-ts Issue Tracker.

mlegenhausen
  • 317
  • 2
  • 4
1

Using TE.Do is of course a solution (clean and small). For another usage, you can just chain your tasks usings pipes.

export const a = (): TE.TaskEither<ErrorA, A> => {
  return TE.right({ a: 123, b: 456, c: 0 });
}

export const b = (): TE.TaskEither<ErrorB, B> => {
  return TE.right({ c: 789 });
}

export const c =(): TE.TaskEither<ErrorA, A> => {
  return pipe(
    a(),
    TE.chain(a => {
      return pipe(
        b(),
        TE.map(b => {
          return {
            ...a,
            c: b.c,
          }
        }),
      );
    }),
  );
}
zenbeni
  • 7,019
  • 3
  • 29
  • 60
  • There is no need to nest the code like that, it's reminiscent of Promise hell. Use `sequenceS` (or `sequenceT`) instead: `pipe(sequenceS(TE.ApplyPar)({ a: a(), b: b()}), TE.map(({a,b}) => ({ ...a, c: b.c })))` – user1713450 Mar 17 '22 at 14:36
0

If you're working with all things of the same applicative (and recall all monads are applicatives), the most straightforward way is:

type A = { /*...*/ }
type B = { /*...*/ }

declare const a: TaskEither<string, A>
declare const b: TaskEither<string, B>

sequenceS(TE.ApplyPar)({ a, b }) // TaskEither<string, { a: A, b: B }>; could also use TE.ApplySeq instead for sequential instead of parallel processing of async calls

the TaskEither will produce left if either a or b is a left.

The sequence variations (there are three, Apply.sequenceS, Apply.sequenceT, and Array.sequence) specifically exist to do what you are asking: take a record/array of monads and result in a record/array of their results if they all "succeed". Otherwise you get a single "failure."

declare const firstOpt: () => Option<string>
declare const secondOpt: () => Option<number>
sequenceT(Option.Apply)(firstOpt(), secondOpt())
// None or Option<[string, number]>

declare const firstEither: () => Left<string>
declare const secondEither: () => Right<number>
sequenceS(Either.Apply)({
  first: firstEither(),
  second: secondEither(),
})
// Left<string>

etc.

user1713450
  • 1,307
  • 7
  • 18