From a8a660aa8ddf22d69d82ffbe2c4a64e5f3ebfd0a Mon Sep 17 00:00:00 2001 From: vinassefranche Date: Fri, 4 Mar 2022 14:26:01 +0100 Subject: [PATCH] Add exercise on parallel and sequential traversing --- src/exo5/exo5.test.ts | 17 +++++++ src/exo5/exo5.ts | 114 ++++++++++++++++++++++++++++++++---------- 2 files changed, 104 insertions(+), 27 deletions(-) diff --git a/src/exo5/exo5.test.ts b/src/exo5/exo5.test.ts index c074008..a13e040 100644 --- a/src/exo5/exo5.test.ts +++ b/src/exo5/exo5.test.ts @@ -2,6 +2,8 @@ import { option } from 'fp-ts'; import { getValidCountryCodeOfCountryNames, giveCurrencyOfCountryToUser, + performAsyncComputationInParallel, + performAsyncComputationInSequence, } from './exo5'; describe('exo5', () => { @@ -38,4 +40,19 @@ describe('exo5', () => { expect(result).toStrictEqual(option.none); }); }); + + describe('performAsyncComputationInParallel', () => { + it('should return the same value for each element with the same value', async () => { + const result = await performAsyncComputationInParallel([1, 1, 1])(); + + expect(result).toStrictEqual([1, 1, 1]); + }); + }); + describe('performAsyncComputationInSequence', () => { + it('should return an increasing value for each element with the same value', async () => { + const result = await performAsyncComputationInSequence([1, 1, 1])(); + + expect(result).toStrictEqual([1, 2, 3]); + }); + }); }); diff --git a/src/exo5/exo5.ts b/src/exo5/exo5.ts index 77257a1..e19226a 100644 --- a/src/exo5/exo5.ts +++ b/src/exo5/exo5.ts @@ -6,9 +6,19 @@ import { pipe } from 'fp-ts/lib/function'; import { Option } from 'fp-ts/lib/Option'; import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord'; import { Task } from 'fp-ts/lib/Task'; -import { unimplemented, unimplementedAsync } from '../utils'; +import { sleep, unimplemented, unimplementedAsync } from '../utils'; -// TBD +// When using many different Functors in a complex application, we can easily +// get to a point when we have many nested types that we would like to 'merge', +// like `Task>>` or `Either>>` +// It would be nice to have a way to 'move up' the similar types in order to +// chain them, like merging the `Task` to have a `Task>` or the +// `Either` to have a `Either>` +// +// That's precisely the concept of `traverse`. It will allow us to transform +// a `Option>` to a `Task>` so we can chain it with another +// `Task` for example, or to transform a `ReadonlyArray>` to a +// `Either>` /////////////////////////////////////////////////////////////////////////////// // SETUP // @@ -25,10 +35,9 @@ export const countryNameToCountryCode: ReadonlyRecord = { // Let's simulate the call to an api which would return the currency when // providing a country code. For the sake of simplicity, let's consider that it -// cannot fail so the signature is -// `getCountryCurrency: (countryCode: CountryCode) => Task` +// cannot fail. type Currency = 'EUR' | 'DOLLAR'; -export const getCountryCurrency = +export const getCountryCurrency: (countryCode: CountryCode) => Task = (countryCode: CountryCode): Task => async () => { if (countryCode === 'US') { @@ -37,19 +46,19 @@ export const getCountryCurrency = return 'EUR'; }; -// Let's simulate a request to the user to provide a country name +// Let's simulate a way for the user to provide a country name. // Let's consider that it cannot fail and let's add the possibility to set -// user's response as a parameter for easier testing -// `getCountryNameFromUser: (countryName: string) => Task` -export const getCountryNameFromUser = (countryName: string) => - task.of(countryName); +// the user's response as a parameter for easier testing. +export const getCountryNameFromUser: (countryName: string) => Task = ( + countryName: string, +) => task.of(countryName); // Here's a function to retrieve the countryCode from a country name if it is -// matching a country we support. This method returns an `option` as we cannot +// matching a country we support. This method returns an `Option` as we cannot // return anything if the given string is not matching a country name we know -// `getCountryCode: (countryName: string) => Option` -export const getCountryCode = (countryName: string) => - readonlyRecord.lookup(countryName)(countryNameToCountryCode); +export const getCountryCode: (countryName: string) => Option = ( + countryName: string, +) => readonlyRecord.lookup(countryName)(countryNameToCountryCode); /////////////////////////////////////////////////////////////////////////////// // TRAVERSING OPTIONS // @@ -57,8 +66,8 @@ export const getCountryCode = (countryName: string) => // With all these functions, we can simulate a program that would ask for a // country name and return its currency if it knows the country. -// A naive implementation would be mapping on each `task` and `option` to call the -// correct method: +// A naive implementation would be mapping on each `Task` and `Option` to call +// the correct method: export const naiveGiveCurrencyOfCountryToUser = ( countryNameFromUserMock: string, ) => @@ -68,12 +77,13 @@ export const naiveGiveCurrencyOfCountryToUser = ( task.map(option.map(getCountryCurrency)), ); // The result type of this method is: `Task>>` -// 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 +// 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 // currency. +// Let's do better than that! -// Use traverse to implement giveCurrencyOfCountryToUser below which returns -// a Task>. +// Use `traverse` to implement `giveCurrencyOfCountryToUser` below which returns +// a `Task>`. // // HINT: Take a look at `option.traverse` to transform an `Option` to // a `Task