This repository has been archived on 2023-05-29. You can view files and clone it, but cannot push or open issues or pull requests.
fp-ts-training/src/exo6/exo6.ts

117 lines
4.1 KiB
TypeScript

// `fp-ts` training Exercise 6
// Introduction to `ReaderTaskEither`
import { ReaderTaskEither } from "fp-ts/lib/ReaderTaskEither";
import { Application } from "./application";
import { User } from "./domain";
import { flow, pipe } from "fp-ts/lib/function";
import { readerTaskEither as rte } from "fp-ts";
// In real world applications you will mostly manipulate `ReaderTaskEither` aka
// `rte` in the use-cases of the application.
// `Reader` -> For dependency injection
// `Task` -> For async operation
// `Either` -> For computations that may fail
//
// Keep in mind, a ReaderTaskEither is nothing more than a Reader of a Task of an Either
// type ReaderTaskEither<Env, Error, Value> = Reader<Env, Task<Either<Error, Value>>>
//
// The ReaderTaskEither module from fp-ts gives us some useful methods to manipulate it.
// You will learn the usage of the most common in the following usecases.
// In the following usecase, you will learn the usage of `rte.map()`.
// `rte.map()` allows you to perform an operation on the values stored in the
// current context. In the following example, we need to fetch a user by its id
// and then we want to return its capitalized.
const capitalizeUserName: (user: User.User) => string = (user) =>
user.name[0].toUpperCase() + user.name.slice(1);
export const getCapitalizedUserName: (args: {
userId: string;
}) => ReaderTaskEither<
User.Repository.Access,
User.Repository.UserNotFoundError,
string
> = ({ userId }) =>
pipe(
User.Repository.getById(userId),
rte.map(capitalizeUserName),
);
// Sometimes you will need to get multiple data points before performing an operation
// on them. In this case, it is very convenient to use the `Do` notation.
//
// The `Do` notation allows you to enrich the context step-by-step by binding
// the result of an effect (in this case a RTE) to a named variable using
// `rte.apS` or `rte.apSW`.
//
// For example:
// pipe(
// rte.Do,
// rte.apS('myDataOne', DataSource.getById(x)),
// ...
// )
export const getConcatenationOfTheTwoUserNames: (args: {
userIdOne: string;
userIdTwo: string;
}) => ReaderTaskEither<
User.Repository.Access,
User.Repository.UserNotFoundError,
string
> = ({ userIdOne, userIdTwo }) =>
pipe(
rte.Do,
rte.apS("userOneName", getCapitalizedUserName({ userId: userIdOne })),
rte.apS("userTwoName", getCapitalizedUserName({ userId: userIdTwo })),
rte.map(({ userOneName, userTwoName }) => userOneName + userTwoName),
);
// Sometimes, you will need to feed the current context with data that you can
// only retrieve after performing some operations, in other words, operations
// need to be sequential.
// For example, if you want to fetch the best friend of a user you will have to
// first fetch the user and then fetch their best friend.
// In this case, we will use `rte.bindW()` to use data of the current context
// (the firstly fetched user) to perform a second operation (fetch their best friend)
// and bind the return value to feed the context and use this data.
export const getConcatenationOfTheBestFriendNameAndUserName: (args: {
userIdOne: string;
}) => ReaderTaskEither<
User.Repository.Access,
User.Repository.UserNotFoundError,
string
> = ({ userIdOne }) =>
pipe(
rte.Do,
rte.apS("user", User.Repository.getById(userIdOne)),
rte.bindW("bestFriend", ({ user }) =>
User.Repository.getById(user.bestFriendId)),
rte.map(({ user, bestFriend }) =>
capitalizeUserName(user) + capitalizeUserName(bestFriend)
),
);
// Most of the time, you will need to use several external services.
// The challenge of this usecase is to use TimeService in the flow of our `rte`
type Dependencies = User.Repository.Access & Application.TimeService.Access;
export const getConcatenationOfUserNameAndCurrentYear: (args: {
userIdOne: string;
}) => ReaderTaskEither<
Dependencies,
User.Repository.UserNotFoundError,
string
> = ({ userIdOne }) =>
pipe(
rte.Do,
rte.apS("user", User.Repository.getById(userIdOne)),
rte.bindW(
"currentYear",
flow(Application.TimeService.thisYear, rte.fromReader),
),
rte.map(({ user, currentYear }) => user.name + currentYear),
);