Skip to content

Instantly share code, notes, and snippets.

@chestercharles
Last active July 19, 2023 09:43
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chestercharles/75601b61f8228496282fbe839f11fe79 to your computer and use it in GitHub Desktop.
Save chestercharles/75601b61f8228496282fbe839f11fe79 to your computer and use it in GitHub Desktop.
Chain vs. Map with TaskEither
import * as TE from 'fp-ts/lib/TaskEither';
import * as E from 'fp-ts/lib/Either';
import { pipe } from 'fp-ts/lib/function';
// Let's start with a value of 4 wrapped up in a TaskEither
const teFour = TE.right(4);
// The type of teFour is TE.TaskEither<never, number> because TE.right knows there will _never_ be a left value (as of yet)
// We can use TE.map to create a function that will accept a TE and operate on it's value
const addOne = TE.map<number, number>(num => num + 1);
const teFive = addOne(teFour); // TE.TaskEither<never, number>
// If we define the addOne function inline, we don't need to annotate - TS will infer
const alsoTeFive = pipe(
teFour,
TE.map(num => num + 1)
); // TE.TaskEither<never, number>
// This is an equivalient expression to the one above
const alsoAlsoTeFive = TE.taskEither.map(teFour, n => n + 1); // TE.TaskEither<never, number>
// Now, lets say we have a function that takes a value, but then returns a TE, like a DB query
// (FYI, TE.tryCatch is a utility that takes a thunk returning a promise, and then puts the promise's resolution value into a TE)
const checkIfEven = (num: number) => TE.tryCatch(() => Promise.resolve(num % 2 == 0), E.toError);
const isTwoEven = checkIfEven(2); // TE.TaskEither<Error, boolean>
// If we want to apply this function to a value inside of a TE:
const isFiveEven = pipe(
teFive,
TE.map(five => checkIfEven(five))
);
// However, the type of isFiveEven is TE.TaskEither<never, TE.TaskEither<Error, boolean>>, which is double wrapped!
// We can use chain to flatMap the TE.TaskEither<Error, boolean>
const isFiveEvenAgain = pipe(
teFive,
TE.chain(five => checkIfEven(five))
); // TE.TaskEither<Error, boolean>
// This is equivalent to the above
const isFiveEvenAgainAgain = TE.taskEither.chain(teFive, five => checkIfEven(five));
// We can also define the function alone, but we need to annotate it
const isNumberEven = TE.chain<Error, number, boolean>(num => checkIfEven(num));
const isFiveEvenAgainAgainAgain = pipe(teFive, isNumberEven); // TE.TaskEither<Error, boolean>
// or equivalently
const isFiveEvenAgainAgainAgainAgain = isNumberEven(teFive); // TE.TaskEither<Error, boolean>
// Sometimes it is nice to define an interface for a function before writing it so that you
// know what you've written is what you expected
// This is equivalent to the above function isNumberEven
type IIsNumberEven = (num: TE.TaskEither<Error, number>) => TE.TaskEither<Error, boolean>;
const isNumberEvenAgain: IIsNumberEven = TE.chain(five => checkIfEven(five));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment