Optimizing performance involves delaying the execution of fold
to minimize unnecessary nesting.
The beauty of algebraic effects is that they can be combined seamlessly without having to determine whether a value exists or an operation has failed.
Consider a scenario where a TaskEither
instance, manipulated using chain
, retains the initial error if no user is found. Otherwise, it holds either the error returned by fetchUser
or the data of the User
in case of success.
See working example here
import { pipeable as P, option as O, taskEither as TE, nonEmptyArray as NA } from "fp-ts";
type User = { name: string };
// Global state (side effect)
let user: string | undefined = undefined;
const setUser = (usr: string) => (user = usr);
// Input provided
const userId: O.Option<string> = O.some("Arun");
const fetchUser: (uid: string) => TE.TaskEither<NA.NonEmptyArray<string>, User> = uid =>
TE.taskEither.of({ name: "Arun" });
// An error case would be: TE.left(NA.of("Unable to fetch user"))
const res = P.pipe(
userId,
TE.fromOption(() => NA.of("No user found")),
TE.chain(fetchUser),
TE.map(user => JSON.stringify(user, null, 2)),
TE.fold( // Performing fold only at the end before applying the effect
err => TE.taskEither.fromIO(() => { setUser(err[0]); }),
user => TE.taskEither.fromIO(() => { setUser(JSON.stringify(user, null, 2)); })
),
TE.chain(() => TE.taskEither.fromIO(() => { console.log(user); }))
);
// Execute the effect
res();
PS: It's assumed here that your fetchUser
represents an asynchronous operation generating a TaskEither
. You can switch it back to Either
if necessary.