Rename apEitherK to apSEitherK + fix some typos (#127)

* exo8: ap -> apS

* Fix typos
This commit is contained in:
Gabriel Théron 2023-05-12 17:35:11 +02:00 committed by GitHub
parent 93139b9b7b
commit f867fef92d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 36 additions and 37 deletions

View file

@ -2,7 +2,7 @@
// Composing computations with `pipe` and `flow` // Composing computations with `pipe` and `flow`
// Functional programming is all about composing small functions together like // Functional programming is all about composing small functions together like
// lego bricks to build more and more complex computations. // Lego bricks to build more and more complex computations.
// //
// Strictly speaking, composing two functions `f` and `g` means applying the // Strictly speaking, composing two functions `f` and `g` means applying the
// result of the first one to the second one. By applying this composition // result of the first one to the second one. By applying this composition

View file

@ -20,14 +20,13 @@ export const divide = (a: number, b: number): number => {
// Write the safe version (meaning it handles the case where b is 0) of `divide` with signature: // Write the safe version (meaning it handles the case where b is 0) of `divide` with signature:
// safeDivide : (a: number, b: number) => Option<number> // safeDivide : (a: number, b: number) => Option<number>
// //
// HINT: Option has two basic contructors: // HINT: Option has two basic constructors:
// - `option.some(value)` // - `option.some(value)`
// - `option.none` // - `option.none`
export const safeDivide: (a: number, b: number) => Option<number> = export const safeDivide: (a: number, b: number) => Option<number> =
unimplemented; unimplemented;
// You probably wrote `safeDivide` using `if` statements and it's perfectly valid! // You probably wrote `safeDivide` using `if` statements and it's perfectly valid!
// There are ways to not use `if` statements. // There are ways to not use `if` statements.
// Keep in mind that extracting small functions out of pipes and using `if` statements in them // Keep in mind that extracting small functions out of pipes and using `if` statements in them
@ -37,7 +36,6 @@ export const safeDivide: (a: number, b: number) => Option<number> =
// //
// HINT: Have a look at `fromPredicate` constructor // HINT: Have a look at `fromPredicate` constructor
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// EITHER // // EITHER //
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////

View file

@ -10,7 +10,7 @@ import { unimplemented } from '../utils';
// SETUP // // SETUP //
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// We are developping a small game, and the player can control either one of // We are developing a small game, and the player can control either one of
// three types of characters, mainly differentiated by the type of damage they // three types of characters, mainly differentiated by the type of damage they
// can put out. // can put out.
@ -133,7 +133,7 @@ export const checkTargetAndShoot: (
// OPTION // // OPTION //
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// The next three function take a `Character` and optionally return the // The next three functions take a `Character` and optionally return the
// expected damage type if the unit matches the expected character type. // expected damage type if the unit matches the expected character type.
// //
// HINT: These functions represent the public API. But it is heavily // HINT: These functions represent the public API. But it is heavily

View file

@ -76,6 +76,7 @@ export const naiveGiveCurrencyOfCountryToUser = (
task.map(getCountryCode), task.map(getCountryCode),
task.map(option.map(getCountryCurrency)), task.map(option.map(getCountryCurrency)),
); );
// The result type of this method is: `Task<Option<Task<Currency>>>` // The result type of this method is: `Task<Option<Task<Currency>>>`
// Not ideal, right? We would need to await the first `Task`, then check if it's // Not ideal, right? We would need to await the first `Task`, then check if it's
// `Some` to get the `Task` inside and finally await the `Task` to retrieve the // `Some` to get the `Task` inside and finally await the `Task` to retrieve the
@ -103,7 +104,7 @@ export const getCountryCurrencyOfOptionalCountryCode: (
// `Task<Option<Currency>>` // `Task<Option<Currency>>`
// //
// HINT: You should be able to copy the pipe from naiveGiveCurrencyOfCountryToUser // HINT: You should be able to copy the pipe from naiveGiveCurrencyOfCountryToUser
// and make only few updates of it. `task.chain` helper may be usefull. // and make only few updates of it. The `task.chain` helper may be useful.
export const giveCurrencyOfCountryToUser: ( export const giveCurrencyOfCountryToUser: (
countryNameFromUserMock: string, countryNameFromUserMock: string,
@ -123,6 +124,7 @@ export const giveCurrencyOfCountryToUser: (
export const getCountryCodeOfCountryNames = ( export const getCountryCodeOfCountryNames = (
countryNames: ReadonlyArray<string>, countryNames: ReadonlyArray<string>,
) => countryNames.map(getCountryCode); ) => countryNames.map(getCountryCode);
// As expected, we end up with a `ReadonlyArray<Option<CountryCode>>`. We know for // As expected, we end up with a `ReadonlyArray<Option<CountryCode>>`. We know for
// each item of the array if we have been able to find the corresponding country // each item of the array if we have been able to find the corresponding country
// code or not. // code or not.
@ -134,7 +136,7 @@ export const getCountryCodeOfCountryNames = (
// Doing this allows you to stop the process if you have a `None` to tell the user // Doing this allows you to stop the process if you have a `None` to tell the user
// that some countries are not valid or move on with a `ReadonlyArray<CountryCode>>` // that some countries are not valid or move on with a `ReadonlyArray<CountryCode>>`
// if all are valid. // if all are valid.
// Typewise, it means going from `ReadonlyArray<Option<CountryCode>>` to // Type-wise, it means going from `ReadonlyArray<Option<CountryCode>>` to
// `Option<ReadonlyArray<CountryCode>>` // `Option<ReadonlyArray<CountryCode>>`
// This is what traversing array is about. // This is what traversing array is about.
@ -208,7 +210,7 @@ export const performAsyncComputationInSequence: (
// `Option<Task>` in our example) // `Option<Task>` in our example)
// Sometimes, you just have two nested containers that you want to 'invert'. It // Sometimes, you just have two nested containers that you want to 'invert'. It
// can be because the order of containers is meaningful (like `Either<Option>` // can be because the order of containers is meaningful (like `Either<Option>`
// and `Option<Either>`) because you got them from an external api, as // and `Option<Either>`) or because you got them from an external api, as
// in the examples. // in the examples.
// In that case, what you need is `sequence`, which you can find in the modules // In that case, what you need is `sequence`, which you can find in the modules
// that have `traverse`. // that have `traverse`.

View file

@ -12,7 +12,7 @@ import { User } from './domain';
// `Task` -> For async operation // `Task` -> For async operation
// `Either` -> For computations that may fail // `Either` -> For computations that may fail
// //
// Keep in Mind, A ReaderTaskEither is nothing more than a Reader of a Task of an Either // 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>>> // 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. // The ReaderTaskEither module from fp-ts gives us some useful methods to manipulate it.
@ -23,7 +23,6 @@ import { User } from './domain';
// current context. In the following example, we need to fetch a user by its id // current context. In the following example, we need to fetch a user by its id
// and then we want to return its capitalized. // and then we want to return its capitalized.
export const getCapitalizedUserName: (args: { export const getCapitalizedUserName: (args: {
userId: string; userId: string;
}) => ReaderTaskEither< }) => ReaderTaskEither<
@ -32,7 +31,7 @@ export const getCapitalizedUserName: (args: {
string string
> = unimplemented; > = unimplemented;
// Sometimes you will need to get multiple data before performing an operation // 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. // 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 `Do` notation allows you to enrich the context step-by-step by binding
@ -59,10 +58,10 @@ export const getConcatenationOfTheTwoUserNames: (args: {
// only retrieve after performing some operations, in other words, operations // only retrieve after performing some operations, in other words, operations
// need to be sequential. // need to be sequential.
// For example, if you want to fetch the best friend of a user you will have to // For example, if you want to fetch the best friend of a user you will have to
// fetch the first user and then fetch its best friend. // 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 // 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 its best friend) // (the firstly fetched user) to perform a second operation (fetch their best friend)
// and bind the return value to feed the context and use those data. // and bind the return value to feed the context and use this data.
export const getConcatenationOfTheBestFriendNameAndUserName: (args: { export const getConcatenationOfTheBestFriendNameAndUserName: (args: {
userIdOne: string; userIdOne: string;

View file

@ -14,7 +14,7 @@ import { unimplemented } from '../utils';
// In fact, it can sometimes be helpful to think of `Set` as a special case // In fact, it can sometimes be helpful to think of `Set` as a special case
// of `Map` where `Set<T>` is strictly equivalent to `Map<T, void>`. // of `Map` where `Set<T>` is strictly equivalent to `Map<T, void>`.
// //
// To manipulate these collections, we need often need to inform `fp-ts` on // To manipulate these collections, we often need to inform `fp-ts` on
// how to uphold the properties outlined above (eg. how to determine whether // how to uphold the properties outlined above (eg. how to determine whether
// two elements or keys have the same value, how to combine values together // two elements or keys have the same value, how to combine values together
// in case of key collision or how to order the values when converting back // in case of key collision or how to order the values when converting back
@ -43,7 +43,7 @@ export const numberArray: ReadonlyArray<number> = [7, 42, 1337, 1, 0, 1337, 42];
export const numberSet: ReadonlySet<number> = unimplemented(); export const numberSet: ReadonlySet<number> = unimplemented();
// Convert `numberSet` back to an an array in `numberArrayFromSet`. // Convert `numberSet` back to an array in `numberArrayFromSet`.
// You need to use the `ReadonlySet` module from `fp-ts` instead of the // You need to use the `ReadonlySet` module from `fp-ts` instead of the
// JavaScript standard constructor. // JavaScript standard constructor.
// //
@ -118,7 +118,7 @@ export const mapWithLastEntry: ReadonlyMap<number, string> = unimplemented();
// The `string` module may contain what you need ;) // The `string` module may contain what you need ;)
// //
// Bonus point: // Bonus point:
// Did you find something helpful in the `Semigroup` module that may have been // Did you find something in the `Semigroup` module that may have been
// helpful in defining `mapWithLastEntry`? // helpful in defining `mapWithLastEntry`?
export const mapWithConcatenatedEntries: ReadonlyMap<number, string> = export const mapWithConcatenatedEntries: ReadonlyMap<number, string> =
@ -135,7 +135,7 @@ export const odds = new Set([1, 3, 5, 7, 9]);
// only include the odd numbers that are not prime. // only include the odd numbers that are not prime.
// //
// HINT: // HINT:
// - Be mindful of the order of operands for the operator you will chose. // - Be mindful of the order of operands for the operator you will choose.
export const nonPrimeOdds: ReadonlySet<number> = unimplemented(); export const nonPrimeOdds: ReadonlySet<number> = unimplemented();
@ -151,7 +151,7 @@ export type Analytics = {
views: number; views: number;
}; };
// These example maps are voluntarily written in a non fp-ts way to not give // These example Maps are voluntarily written in a non fp-ts way to not give
// away too much obviously ;) // away too much obviously ;)
// //
// As an exercise for the reader, they may rewrite those with what they've // As an exercise for the reader, they may rewrite those with what they've
@ -173,14 +173,14 @@ export const pageViewsB = new Map(
].map(entry => [entry.page, entry]), ].map(entry => [entry.page, entry]),
); );
// Construct the map with the total page views for all the pages in both sources // Construct the `Map` with the total page views for all the pages in both sources
// of analytics `pageViewsA` and `pageViewsB`. // of analytics `pageViewsA` and `pageViewsB`.
// //
// In case a page appears in both sources, their view count should be summed. // In case a page appears in both sources, their view count should be summed.
export const allPageViews: ReadonlyMap<string, Analytics> = unimplemented(); export const allPageViews: ReadonlyMap<string, Analytics> = unimplemented();
// Construct the map with the total page views but only for the pages that // Construct the `Map` with the total page views but only for the pages that
// appear in both sources of analytics `pageViewsA` and `pageViewsB`. // appear in both sources of analytics `pageViewsA` and `pageViewsB`.
export const intersectionPageViews: ReadonlyMap<string, Analytics> = export const intersectionPageViews: ReadonlyMap<string, Analytics> =

View file

@ -1,8 +1,8 @@
import { either, reader, readerTaskEither as rte } from 'fp-ts'; import { either, reader, readerTaskEither as rte } from 'fp-ts';
import { pipe } from 'fp-ts/function'; import { pipe } from 'fp-ts/function';
import { import {
apEitherK as rteApEitherK, apSEitherK as rteApSEitherK,
apEitherKW as rteApEitherKW, apSEitherKW as rteApSEitherKW,
bindEitherK as rteBindEitherK, bindEitherK as rteBindEitherK,
bindEitherKW as rteBindEitherKW, bindEitherKW as rteBindEitherKW,
bindReaderK as rteBindReaderK, bindReaderK as rteBindReaderK,
@ -52,12 +52,12 @@ describe('exo8', () => {
}); });
}); });
describe('apEitherK[W]', () => { describe('apSEitherK[W]', () => {
it('should be usable in a happy path do-notation rte pipeline', async () => { it('should be usable in a happy path do-notation rte pipeline', async () => {
const pipeline = await pipe( const pipeline = await pipe(
rte.Do, rte.Do,
rte.apS('foo', rte.of(42)), rte.apS('foo', rte.of(42)),
rteApEitherK('bar', either.right(1337)), rteApSEitherK('bar', either.right(1337)),
)({})(); )({})();
const expected = await rte.of({ foo: 42, bar: 1337 })({})(); const expected = await rte.of({ foo: 42, bar: 1337 })({})();
@ -69,8 +69,8 @@ describe('exo8', () => {
const pipeline = await pipe( const pipeline = await pipe(
rte.Do, rte.Do,
rte.apS('foo', rte.of<unknown, string, number>(42)), rte.apS('foo', rte.of<unknown, string, number>(42)),
rteApEitherK('bar', either.right(1337)), rteApSEitherK('bar', either.right(1337)),
rteApEitherK('baz', either.left(`Error!`)), rteApSEitherK('baz', either.left(`Error!`)),
)({})(); )({})();
const expected = await rte.left('Error!')({})(); const expected = await rte.left('Error!')({})();
@ -82,8 +82,8 @@ describe('exo8', () => {
const pipeline = await pipe( const pipeline = await pipe(
rte.Do, rte.Do,
rte.apS('foo', rte.of<unknown, string, number>(42)), rte.apS('foo', rte.of<unknown, string, number>(42)),
rteApEitherK('bar', either.right(1337)), rteApSEitherK('bar', either.right(1337)),
rteApEitherKW('baz', either.left(0)), rteApSEitherKW('baz', either.left(0)),
)({})(); )({})();
const expected = await rte.left(0)({})(); const expected = await rte.left(0)({})();

View file

@ -12,7 +12,7 @@ import { unimplemented } from '../utils';
// `Option`, `Either` and the likes such as `map`, `chain` and so on as // `Option`, `Either` and the likes such as `map`, `chain` and so on as
// combinators. // combinators.
// //
// The fp-ts library provides a rich collection for such combinators for each // The fp-ts library provides a rich collection of such combinators for each
// type and module but sometimes, you may want to reach for a combinator that // type and module but sometimes, you may want to reach for a combinator that
// doesn't yet exist in the library and it is useful to know how to define // doesn't yet exist in the library and it is useful to know how to define
// your own. // your own.
@ -82,22 +82,22 @@ export const bindEitherK: <N extends string, A, E, B>(
export const bindEitherKW = unimplemented; export const bindEitherKW = unimplemented;
// Write the implementations and type definitions of `apEitherK` and // Write the implementations and type definitions of `apSEitherK` and
// `apEitherKW`. // `apSEitherKW`.
// //
// HINT: // HINT:
// - remember that "widen" in the case of `Either` means the union of the // - remember that "widen" in the case of `Either` means the union of the
// possible error types // possible error types
export const apEitherK = unimplemented; export const apSEitherK = unimplemented;
export const apEitherKW = unimplemented; export const apSEitherKW = unimplemented;
// Write the implementations and type definitions of `bindReaderK` and // Write the implementations and type definitions of `bindReaderK` and
// `bindReaderKW`. // `bindReaderKW`.
// //
// HINT: // HINT:
// - remember that "widen" in the case of `Reader` means the interesection of // - remember that "widen" in the case of `Reader` means the intersection of
// the possible environment types // the possible environment types
export const bindReaderK = unimplemented; export const bindReaderK = unimplemented;