Fp-ts: taskEither.fold works differently across v1 and v2

Created on 19 Jul 2019  路  2Comments  路  Source: gcanti/fp-ts

I was trying to refactor my v1 code to v2 and I noticed that the behavior of fold has changed between versions:

Here's perfectly valid 1.1 code

const v1 = taskEither.of(42).fold(err => 42, response => response + 1); // typeof v1 => Task<number>

Wheras with 2.0, fold requires me to return a Task instead, which kind of makes it a bit inconvenient to work with.

const v2 = pipe(
  taskEither.of(42),
  fold(() => 42, response => response + 1)
); 

Given the constraints, there are two approaches that seem like the most appropriate translation:

const v2_1 = pipe(
  taskEither.of(42),
  fold(() => task.of(42), response => task.of(response + 1))
);

const v2_2 = await taskEither.of(42)();
pipe(
  v2_2,
  either.fold(() => 42, response => response + 1)
);

I'd be interested in understanding the motivation for the change and your thoughts on how best to go about refactoring such scenarios.

Edit: Pointfree functions in the vein of fold(props.onFailed, props.onSuccess) were very convenient to have directly on a TaskEither with v1, but they become a bit verbose with the v2_2 approach

Most helpful comment

The rationale was to reduce the API surface (now allowed by the class-less encoding). With the new fold we get the same functionality of the old

  • fold
  • foldTask
  • foldTaskEither

methods.

Note that v2_2 is not ok since you are executing the side effects.

Here's some options:

1) using TE.fold (your v2_1)

import { pipe } from 'fp-ts/lib/pipeable'
import * as T from 'fp-ts/lib/Task'
import * as TE from 'fp-ts/lib/TaskEither'

const t = pipe(
  TE.right(42),
  TE.fold(() => T.of(42), response => T.of(response + 1))
)

2) T.map + E.fold

import * as E from 'fp-ts/lib/Either'
import { pipe } from 'fp-ts/lib/pipeable'
import * as T from 'fp-ts/lib/Task'
import * as TE from 'fp-ts/lib/TaskEither'

const t = pipe(
  TE.right(42),
  T.map(E.fold(() => 42, response => response + 1))
)

3) derive the old fold signature (probably the most convenient)

import * as E from 'fp-ts/lib/Either'
import { flow } from 'fp-ts/lib/function'
import { pipe } from 'fp-ts/lib/pipeable'
import * as T from 'fp-ts/lib/Task'
import * as TE from 'fp-ts/lib/TaskEither'

const oldFold = flow(
  E.fold,
  T.map
)

const t = pipe(
  TE.right(42),
  oldFold(() => 42, response => response + 1)
)

4) TE.map + TE.getOrElse

import { pipe } from 'fp-ts/lib/pipeable'
import * as T from 'fp-ts/lib/Task'
import * as TE from 'fp-ts/lib/TaskEither'

const t = pipe(
  TE.right(42),
  TE.map(response => response + 1),
  TE.getOrElse(() => T.of(42))
)

All 2 comments

The rationale was to reduce the API surface (now allowed by the class-less encoding). With the new fold we get the same functionality of the old

  • fold
  • foldTask
  • foldTaskEither

methods.

Note that v2_2 is not ok since you are executing the side effects.

Here's some options:

1) using TE.fold (your v2_1)

import { pipe } from 'fp-ts/lib/pipeable'
import * as T from 'fp-ts/lib/Task'
import * as TE from 'fp-ts/lib/TaskEither'

const t = pipe(
  TE.right(42),
  TE.fold(() => T.of(42), response => T.of(response + 1))
)

2) T.map + E.fold

import * as E from 'fp-ts/lib/Either'
import { pipe } from 'fp-ts/lib/pipeable'
import * as T from 'fp-ts/lib/Task'
import * as TE from 'fp-ts/lib/TaskEither'

const t = pipe(
  TE.right(42),
  T.map(E.fold(() => 42, response => response + 1))
)

3) derive the old fold signature (probably the most convenient)

import * as E from 'fp-ts/lib/Either'
import { flow } from 'fp-ts/lib/function'
import { pipe } from 'fp-ts/lib/pipeable'
import * as T from 'fp-ts/lib/Task'
import * as TE from 'fp-ts/lib/TaskEither'

const oldFold = flow(
  E.fold,
  T.map
)

const t = pipe(
  TE.right(42),
  oldFold(() => 42, response => response + 1)
)

4) TE.map + TE.getOrElse

import { pipe } from 'fp-ts/lib/pipeable'
import * as T from 'fp-ts/lib/Task'
import * as TE from 'fp-ts/lib/TaskEither'

const t = pipe(
  TE.right(42),
  TE.map(response => response + 1),
  TE.getOrElse(() => T.of(42))
)

Thanks a lot for the very clear explanation :-)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mustafaekim picture mustafaekim  路  4Comments

steida picture steida  路  4Comments

mohsensaremi picture mohsensaremi  路  3Comments

jollytoad picture jollytoad  路  4Comments

miguelferraro picture miguelferraro  路  3Comments