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
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
foldfoldTaskfoldTaskEithermethods.
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 :-)
Most helpful comment
The rationale was to reduce the API surface (now allowed by the
class-less encoding). With the newfoldwe get the same functionality of the oldfoldfoldTaskfoldTaskEithermethods.
Note that
v2_2is not ok since you are executing the side effects.Here's some options:
1) using
TE.fold(yourv2_1)2)
T.map+E.fold3) derive the old
foldsignature (probably the most convenient)4)
TE.map+TE.getOrElse