Last active
July 19, 2023 09:43
-
-
Save chestercharles/75601b61f8228496282fbe839f11fe79 to your computer and use it in GitHub Desktop.
Chain vs. Map with TaskEither
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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