diff --git a/src/exo0 - Composing with pipe and flow/exo0.exercise.ts b/src/exo0 - Composing with pipe and flow/exo0.exercise.ts index 1771dd4..54986cb 100644 --- a/src/exo0 - Composing with pipe and flow/exo0.exercise.ts +++ b/src/exo0 - Composing with pipe and flow/exo0.exercise.ts @@ -24,7 +24,8 @@ // - `flow(f) === f` // - `pipe(x, f) === f(x)` -import { unimplemented } from '../utils'; +import { pipe, flow } from 'fp-ts/lib/function'; +// import { unimplemented } from '../utils'; export const isEven = (value: number) => value % 2 === 0; @@ -42,9 +43,9 @@ export const not = (value: boolean) => !value; // `pipe` or `flow`), write the function `isOdd` that checks if a number is // odd. -export const isOddP: (value: number) => boolean = unimplemented; +export const isOddP = (value: number) => pipe(value, isEven, not); -export const isOddF: (value: number) => boolean = unimplemented; +export const isOddF: (value: number) => boolean = flow(isEven, not); // We will write a function that for any given number, computes the next // one according to the following rules: @@ -63,10 +64,10 @@ export const ifThenElse = // Using `pipe` and `ifThenElse`, write the function that computes the next step in the Collatz // sequence. -export const next: (value: number) => number = unimplemented; +export const next = (value: number) => pipe(value, isOddP, ifThenElse(() => value * 3 + 1, () => value/2)); // Using only `flow` and `next`, write the function that for any given number // a_n from the Collatz sequence, returns the number a_n+3 (i.e. the number // three steps ahead in the sequence). -export const next3: (value: number) => number = unimplemented; +export const next3: (value: number) => number = flow(next, next, next); diff --git a/src/exo1 - Basic types/exo1.exercise.ts b/src/exo1 - Basic types/exo1.exercise.ts index 1512952..30071ec 100644 --- a/src/exo1 - Basic types/exo1.exercise.ts +++ b/src/exo1 - Basic types/exo1.exercise.ts @@ -4,10 +4,10 @@ // - Either // - TaskEither -import { Either } from 'fp-ts/Either'; import { Option } from 'fp-ts/Option'; -import { TaskEither } from 'fp-ts/TaskEither'; -import { unimplemented, sleep, unimplementedAsync } from '../utils'; +import { sleep } from '../utils'; +import { either, option, taskEither } from 'fp-ts'; +import { pipe } from 'fp-ts/lib/function'; export const divide = (a: number, b: number): number => { return a / b; @@ -25,7 +25,23 @@ export const divide = (a: number, b: number): number => { // - `option.none` export const safeDivide: (a: number, b: number) => Option = - unimplemented; + (a: number, b: number) => { + if (b !== 0) { + return option.some(divide(a, b)) + } + return option.none +} + +/* +I used the solution to help me write the version below: + +const isValidDivisor = (n: number): boolean => n !== 0; +export const safeDivide = (a: number, b: number) => + pipe(b, + option.fromPredicate(b => isValidDivisor(b)), + option.map(b => divide(a, b)) + ) +*/ // You probably wrote `safeDivide` using `if` statements, and it's perfectly valid! // There are ways to not use `if` statements. @@ -54,11 +70,19 @@ export const safeDivide: (a: number, b: number) => Option = // Here is a simple error type to help you: export type DivisionByZeroError = 'Error: Division by zero'; export const DivisionByZero = 'Error: Division by zero' as const; - -export const safeDivideWithError: ( + +export const safeDivideWithError = ( a: number, - b: number, -) => Either = unimplemented; + b: number) => + pipe( + b, + either.fromPredicate( + n => n!== 0, + () => DivisionByZero, + ), + either.map(b => a/b) + ) + /////////////////////////////////////////////////////////////////////////////// // TASKEITHER // @@ -72,7 +96,6 @@ export const asyncDivide = async (a: number, b: number) => { if (b === 0) { throw new Error('BOOM!'); } - return a / b; }; @@ -83,7 +106,7 @@ export const asyncDivide = async (a: number, b: number) => { // a TaskEither: // - `taskEither.tryCatch(f: () => promise, onReject: reason => leftValue)` -export const asyncSafeDivideWithError: ( +export const asyncSafeDivideWithError = ( a: number, b: number, -) => TaskEither = unimplementedAsync; +) => taskEither.tryCatch(() => asyncDivide(a, b), () => DivisionByZero) diff --git a/src/exo2 - Combinators/exo2.exercise.ts b/src/exo2 - Combinators/exo2.exercise.ts index 8e843bc..5057e1d 100644 --- a/src/exo2 - Combinators/exo2.exercise.ts +++ b/src/exo2 - Combinators/exo2.exercise.ts @@ -4,7 +4,8 @@ import { Either } from 'fp-ts/Either'; import { Option } from 'fp-ts/Option'; import { Failure } from '../Failure'; -import { unimplemented } from '../utils'; +import { flow, pipe } from 'fp-ts/lib/function'; +import { either, option, readonlyArray } from 'fp-ts'; /////////////////////////////////////////////////////////////////////////////// // SETUP // @@ -117,17 +118,64 @@ export const invalidAttackerFailure = Failure.builder( // common operations done with the `Either` type and it is available through // the `flatMap` operator. -export const checkAttackerAndSmash: ( - attacker: Option, -) => Either = unimplemented; -export const checkAttackerAndBurn: ( - attacker: Option, -) => Either = unimplemented; -export const checkAttackerAndShoot: ( - attacker: Option, -) => Either = unimplemented; +//****** I looked at the solution to get help on this one. **********// + +const isAttackerDefined = + either.fromOption(() => + noAttackerFailure("No attacker currently selected")); + +const isAllowedWarriorAction = + either.fromPredicate(isWarrior, target => + invalidAttackerFailure(`${target.toString()} cannot perform smash`)); + +const isAllowedWizardAction = + either.fromPredicate(isWizard, target => + invalidAttackerFailure(`${target.toString()} cannot perform burn`)); + +const isAllowedArcherAction = + either.fromPredicate(isArcher, target => + invalidAttackerFailure(`${target.toString()} cannot perform shoot`)); + +const smash = flow ( + isAllowedWarriorAction, + either.map(attacker => attacker.smash()) +) + +const burn = flow ( + isAllowedWizardAction, + either.map(attacker => attacker.burn()) +) + +const shoot = flow ( + isAllowedArcherAction, + either.map( attacker => attacker.shoot()) +) + +export const checkAttackerAndSmash = ( + target: Option, +): Either => pipe( + target, + isAttackerDefined, + either.flatMap(smash) +) + +export const checkAttackerAndBurn = ( + target: Option, +): Either => pipe( + target, + isAttackerDefined, + either.flatMap(burn) +); + +export const checkAttackerAndShoot = ( + target: Option, +): Either => pipe ( + target, + isAttackerDefined, + either.flatMap(shoot) +); /////////////////////////////////////////////////////////////////////////////// // OPTION // @@ -146,14 +194,26 @@ export const checkAttackerAndShoot: ( // BONUS POINTS: If you properly defined small private helpers in the previous // section, they should be easily reused for those use-cases. -export const smashOption: (character: Character) => Option = - unimplemented; - -export const burnOption: (character: Character) => Option = - unimplemented; - -export const shootOption: (character: Character) => Option = - unimplemented; +export const smashOption = (character: Character): Option => + pipe ( + character, + smash, + option.fromEither + ); + +export const burnOption = (character: Character) => + pipe ( + character, + burn, + option.fromEither + ); + +export const shootOption = (character: Character) => + pipe ( + character, + shoot, + option.fromEither + ); /////////////////////////////////////////////////////////////////////////////// // ARRAY // @@ -168,11 +228,39 @@ export const shootOption: (character: Character) => Option = // perform mapping and filtering at the same time by applying a function // of type `A => Option` over the collection. + + + +//****** I looked at the solution to get help on this one. **********// + export interface TotalDamage { [Damage.Physical]: number; [Damage.Magical]: number; [Damage.Ranged]: number; } +/* +Why doesn't the code in the line below work as a replacement for option 1 on line 252? + readonlyArray.filterMap(flow(option.filter(isWizard), option.map(character => character))), +*/ + +export const attack = (army: ReadonlyArray) => ({ +[Damage.Physical]: + pipe ( + army, + readonlyArray.filterMap(smashOption), + readonlyArray.size, + ), + [Damage.Magical]: + pipe ( + army, + readonlyArray.filterMap(burnOption), + readonlyArray.size, + ), + [Damage.Ranged]: + pipe ( + army, + readonlyArray.filterMap(shootOption), + readonlyArray.size, + ), +}) -export const attack: (army: ReadonlyArray) => TotalDamage = - unimplemented; diff --git a/src/exo3 - Sort with Ord/exo3.exercise.ts b/src/exo3 - Sort with Ord/exo3.exercise.ts index e6fd00c..2498695 100644 --- a/src/exo3 - Sort with Ord/exo3.exercise.ts +++ b/src/exo3 - Sort with Ord/exo3.exercise.ts @@ -2,7 +2,14 @@ // Sort things out with `Ord` import { Option } from 'fp-ts/Option'; -import { unimplemented } from '../utils'; +import { number, option, ord, readonlyArray, string } from 'fp-ts'; +import { pipe } from 'fp-ts/lib/function'; + + + +/******* I relied heavily on the fp-ts-cheatsheet to solve these problems **********/ + + // Have you ever looked at the methods provided by `fp-ts` own `Array` and // `ReadonlyArray` modules? They expose a load of functions to manipulate @@ -34,13 +41,30 @@ import { unimplemented } from '../utils'; // expose some pre constructed instances of `Ord` for said primitives such as // `string.Ord: Ord` or `number.Ord: Ord`. -export const sortStrings: ( +// export const sortStrings = ( +// strings: ReadonlyArray, +// ): ReadonlyArray => pipe( +// strings, +// readonlyArray.sort(string.Ord) +// ); + +export const sortStrings = ( strings: ReadonlyArray, -) => ReadonlyArray = unimplemented; +): ReadonlyArray => + readonlyArray.sort(string.Ord)(strings); + +// export const sortNumbers = ( +// numbers: ReadonlyArray, +// ): ReadonlyArray => pipe ( +// numbers, +// readonlyArray.sort(number.Ord) +// ); -export const sortNumbers: ( +export const sortNumbers = ( numbers: ReadonlyArray, -) => ReadonlyArray = unimplemented; +): ReadonlyArray => + readonlyArray.sort(number.Ord)(numbers); + /////////////////////////////////////////////////////////////////////////////// // REVERSE SORT // @@ -55,9 +79,11 @@ export const sortNumbers: ( // // HINT: Any ordering can be reversed with a simple function `ord.reverse`. -export const sortNumbersDescending: ( +export const sortNumbersDescending = ( numbers: ReadonlyArray, -) => ReadonlyArray = unimplemented; +): ReadonlyArray => + readonlyArray.sort(ord.reverse(number.Ord))(numbers); + /////////////////////////////////////////////////////////////////////////////// // SORT OPTIONAL VALUES // @@ -73,9 +99,10 @@ export const sortNumbersDescending: ( // of building an `Ord` instance for their qualified inner type. You may want // to take a look at `option.getOrd`. -export const sortOptionalNumbers: ( +export const sortOptionalNumbers = ( optionalNumbers: ReadonlyArray>, -) => ReadonlyArray> = unimplemented; +): ReadonlyArray> => + readonlyArray.sort(option.getOrd(number.Ord))(optionalNumbers); /////////////////////////////////////////////////////////////////////////////// // SORT COMPLEX OBJECTS // @@ -99,13 +126,25 @@ export interface Person { readonly age: Option; } -export const sortPersonsByName: ( +export const sortPersonsByName = ( persons: ReadonlyArray, -) => ReadonlyArray = unimplemented; - -export const sortPersonsByAge: ( +): ReadonlyArray => pipe ( + persons, + readonlyArray.sortBy([pipe( + string.Ord, + ord.contramap((person: Person) => person.name) + )]) +); + +export const sortPersonsByAge = ( persons: ReadonlyArray, -) => ReadonlyArray = unimplemented; +): ReadonlyArray => pipe ( + persons, + readonlyArray.sortBy( [pipe ( + option.getOrd(number.Ord), + ord.contramap((person: Person) => person.age) + )]) +); /////////////////////////////////////////////////////////////////////////////// // COMBINE SORTING SCHEMES // @@ -116,6 +155,18 @@ export const sortPersonsByAge: ( // // HINT: Take a look at `readonlyArray.sortBy` -export const sortPersonsByAgeThenByName: ( +const byAge = pipe ( + option.getOrd(number.Ord), + ord.contramap((person: Person) => person.age) +) + +const byName = pipe ( + string.Ord, + ord.contramap((person: Person) => person.name) +) + +export const sortPersonsByAgeThenByName = ( persons: ReadonlyArray, -) => ReadonlyArray = unimplemented; +): ReadonlyArray => + readonlyArray.sortBy([byAge, byName])( persons); + diff --git a/src/exo4 - Dependency injection with Reader/exo4.exercise.ts b/src/exo4 - Dependency injection with Reader/exo4.exercise.ts index 47bc3e7..db2c73b 100644 --- a/src/exo4 - Dependency injection with Reader/exo4.exercise.ts +++ b/src/exo4 - Dependency injection with Reader/exo4.exercise.ts @@ -1,9 +1,9 @@ // `fp-ts` training Exercise 4 // Dependency injection with `Reader` +import { reader } from 'fp-ts'; import { Reader } from 'fp-ts/Reader'; - -import { unimplemented } from '../utils'; +import { pipe } from 'fp-ts/lib/function'; // Sometimes, a function can have a huge amount of dependencies (services, // repositories, ...) and it is often impractical (not to say truly annoying) @@ -44,8 +44,22 @@ export enum Country { // // HINT: Take a look at `reader.ask` to access the environment value -export const exclamation: (sentence: string) => Reader = - unimplemented(); +export const addPunctuation = (country: Country, sentence: string): string => { + switch (country) { + case Country.France: + return `${sentence} !`; + case Country.Spain: + return `ยก${sentence}!`; + case Country.USA: + return `${sentence}!`; + } +}; + +export const exclamation = (sentence: string): Reader => + pipe( + reader.ask(), + reader.map(country => addPunctuation(country, sentence)) + ); // Obviously, different countries often mean different languages and so // different words for saying "Hello": @@ -70,7 +84,12 @@ export const sayHello = (country: Country): string => { // HINT: You can look into `reader.map` to modify the output of a `Reader` // action. -export const greet: (name: string) => Reader = unimplemented(); +export const greet = (name: string): Reader => + pipe ( + reader.ask(), + reader.map(country => sayHello(country)), + reader.map(hello => `${hello}, ${name}`) + ); // Finally, we are going to compose multiple `Reader`s together. // @@ -84,5 +103,9 @@ export const greet: (name: string) => Reader = unimplemented(); // HINT: As with other wrapper types in `fp-ts`, `reader` offers a way of // composing effects with `reader.flatMap`. -export const excitedlyGreet: (name: string) => Reader = - unimplemented(); +export const excitedlyGreet = (name: string): Reader => + pipe ( + name, + greet, + reader.flatMap(greeting => exclamation(greeting)) + ); diff --git a/src/exo5 - Nested data with traverse/exo5.exercise.ts b/src/exo5 - Nested data with traverse/exo5.exercise.ts index ce170ec..25a1688 100644 --- a/src/exo5 - Nested data with traverse/exo5.exercise.ts +++ b/src/exo5 - Nested data with traverse/exo5.exercise.ts @@ -6,7 +6,7 @@ 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 { sleep, unimplemented, unimplementedAsync } from '../utils'; +import { sleep } from '../utils'; // 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', @@ -96,7 +96,9 @@ export const naiveGiveCurrencyOfCountryToUser = ( export const getCountryCurrencyOfOptionalCountryCode: ( optionalCountryCode: Option, -) => Task> = unimplementedAsync; +) => Task> = + option.traverse(task.ApplicativePar)(getCountryCurrency); + // Let's now use this function in our naive implementation's pipe to see how it // improves it. @@ -106,14 +108,20 @@ export const getCountryCurrencyOfOptionalCountryCode: ( // HINT: You should be able to copy the pipe from naiveGiveCurrencyOfCountryToUser // and make only few updates of it. The `task.flatMap` helper may be useful. -export const giveCurrencyOfCountryToUser: ( +export const giveCurrencyOfCountryToUser = ( countryNameFromUserMock: string, -) => Task> = unimplementedAsync; +): Task> => +pipe ( + getCountryNameFromUser(countryNameFromUserMock), + task.map(getCountryCode), + task.flatMap(countryCode => getCountryCurrencyOfOptionalCountryCode(countryCode)), +); // BONUS: We don't necessarily need `traverse` to do this. Try implementing // `giveCurrencyOfCountryToUser` by lifting some of the functions' results to // `TaskOption` + /////////////////////////////////////////////////////////////////////////////// // TRAVERSING ARRAYS // /////////////////////////////////////////////////////////////////////////////// @@ -146,9 +154,13 @@ export const getCountryCodeOfCountryNames = ( // HINT: while `readonlyArray.traverse` exists, you have a shortcut in the `option` // module: `option.traverseArray` -export const getValidCountryCodeOfCountryNames: ( +export const getValidCountryCodeOfCountryNames = ( countryNames: ReadonlyArray, -) => Option> = unimplemented; +): Option> => + pipe ( + countryNames, + option.traverseArray(getCountryCode) + ); /////////////////////////////////////////////////////////////////////////////// // TRAVERSING ARRAYS ASYNCHRONOUSLY // @@ -184,9 +196,11 @@ const createSimulatedAsyncMethod = (): ((toAdd: number) => Task) => { // module to traverse arrays export const simulatedAsyncMethodForParallel = createSimulatedAsyncMethod(); -export const performAsyncComputationInParallel: ( +export const performAsyncComputationInParallel = ( numbers: ReadonlyArray, -) => Task> = unimplementedAsync; +): Task> => { + return task.traverseArray(simulatedAsyncMethodForParallel)(numbers) +}; // Write a method to traverse an array by running the method // `simulatedAsyncMethodForSequence: (toAdd: number) => Task` @@ -196,9 +210,11 @@ export const performAsyncComputationInParallel: ( // module to traverse arrays export const simulatedAsyncMethodForSequence = createSimulatedAsyncMethod(); -export const performAsyncComputationInSequence: ( +export const performAsyncComputationInSequence = ( numbers: ReadonlyArray, -) => Task> = unimplementedAsync; +): Task> => { + return task.traverseSeqArray(simulatedAsyncMethodForSequence)(numbers) +}; /////////////////////////////////////////////////////////////////////////////// // SEQUENCE // @@ -218,13 +234,17 @@ export const performAsyncComputationInSequence: ( // Use the `sequence` methods from the `option` module to implement the two // functions below -export const sequenceOptionTask: ( +export const sequenceOptionTask = ( optionOfTask: Option>, -) => Task> = unimplementedAsync; +): Task> => + option.sequence(task.ApplicativeSeq)(optionOfTask); + -export const sequenceOptionArray: ( +export const sequenceOptionArray = ( arrayOfOptions: ReadonlyArray>, -) => Option> = unimplemented; +): Option> => + // readonlyArray.sequence(option.Applicative)(arrayOfOptions); + option.sequenceArray(arrayOfOptions) // BONUS: try using these two functions in the exercises 'TRAVERSING OPTIONS' // and 'TRAVERSING ARRAYS' above diff --git a/src/exo6 - ReaderTaskEither/exo6.exercise.ts b/src/exo6 - ReaderTaskEither/exo6.exercise.ts index 670d1ee..c76a17a 100644 --- a/src/exo6 - ReaderTaskEither/exo6.exercise.ts +++ b/src/exo6 - ReaderTaskEither/exo6.exercise.ts @@ -1,10 +1,11 @@ // `fp-ts` training Exercise 6 // Introduction to `ReaderTaskEither` -import { ReaderTaskEither } from 'fp-ts/lib/ReaderTaskEither'; -import { unimplemented } from '../utils'; import { Application } from './application'; import { User } from './domain'; +import { rte } from '../readerTaskEither'; +import { pipe } from 'fp-ts/lib/function'; + // In real world applications you will mostly manipulate `ReaderTaskEither` aka // `rte` in the use-cases of the application. @@ -23,13 +24,18 @@ import { User } from './domain'; // current context. In the following example, we need to fetch a user by its id, // and then we want to return its name capitalized. -export const getCapitalizedUserName: (args: { +const capitalizeName = (name: string) => { + return `${name[0].toUpperCase()}${name.slice(1)}`; +} + +export const getCapitalizedUserName = ({userId}: { userId: string; -}) => ReaderTaskEither< - User.Repository.Access, - User.Repository.UserNotFoundError, - string -> = unimplemented; +}) => pipe( + User.Repository.getById(userId), + rte.map(user => capitalizeName(user.name)) +) + + // 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. @@ -45,33 +51,37 @@ export const getCapitalizedUserName: (args: { // ... // ) -export const getConcatenationOfTheTwoUserNames: (args: { + +export const getConcatenationOfTheTwoUserNames = ({userIdOne, userIdTwo}: { userIdOne: string; userIdTwo: string; -}) => ReaderTaskEither< - User.Repository.Access, - User.Repository.UserNotFoundError, - string -> = unimplemented; +}) => pipe ( + rte.Do, + rte.apS('firstCapitalizedName', getCapitalizedUserName({userId: userIdOne})), + rte.apS('secondCapitalizedName', getCapitalizedUserName({userId: userIdTwo})), + rte.map(({firstCapitalizedName, secondCapitalizedName}) => `${firstCapitalizedName}${secondCapitalizedName}`) +) + // There is an alternative way of writing the previous function without the // Do notation. It consists of "lifting" the concatenation function in a rte // (using `rte.of()` or `rte.right()`) and then applying the (lifted) arguments // one after the other using `rte.ap()`. For this to work, you need to have // a curried version of the concatenation function: -// const concat: (x: string) => (y: string) => string +const concat = (x: string) => (y: string) => x + y; // // Write another version of getConcatenationOfTheTwoUserNames function // using `rte.ap()`: -export const getConcatenationOfTheTwoUserNamesUsingAp: (args: { +export const getConcatenationOfTheTwoUserNamesUsingAp = ({userIdOne, userIdTwo}: { userIdOne: string; userIdTwo: string; -}) => ReaderTaskEither< - User.Repository.Access, - User.Repository.UserNotFoundError, - string -> = unimplemented; +}) => pipe ( + rte.of(concat), + rte.ap(getCapitalizedUserName({userId: userIdOne})), + rte.ap(getCapitalizedUserName({userId: userIdTwo})), +) + // Sometimes, you will need to feed the current context with data that you can // only retrieve after performing some operations, in other words, operations @@ -82,22 +92,36 @@ export const getConcatenationOfTheTwoUserNamesUsingAp: (args: { // (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 -> = unimplemented; +export const getConcatenationOfTheBestFriendNameAndUserName = ({userId}: { + userId: string; +}) => pipe( + rte.Do, + + rte.apS('user', User.Repository.getById(userId)), + rte.bindW('bestFriend', ({user}) => User.Repository.getById(user.bestFriendId)), + rte.map(({user, bestFriend}) => `${capitalizeName(user.name)}${capitalizeName(bestFriend.name)}`) + + // rte.apS('user', User.Repository.getById(userId)), + // rte.apS('userName', getCapitalizedUserName({userId})), + // rte.bindW('bestFriendName', ({user}) => getCapitalizedUserName({userId: user.bestFriendId})), + // rte.map(({userName, bestFriendName}) => userName + bestFriendName), + + ) + // Most of the time, you will need to use several external services. // The challenge of this use-case is to use TimeService in the flow of our `rte` -type Dependencies = User.Repository.Access & Application.TimeService.Access; +// type Dependencies = User.Repository.Access & Application.TimeService.Access; -export const getConcatenationOfUserNameAndCurrentYear: (args: { +export const getConcatenationOfUserNameAndCurrentYear = ({userIdOne}: { userIdOne: string; -}) => ReaderTaskEither< - Dependencies, - User.Repository.UserNotFoundError, - string -> = unimplemented; +}) => pipe ( + rte.Do, + rte.apS('user', User.Repository.getById(userIdOne)), + rte.apSW('thisYear', rte.fromReader(Application.TimeService.thisYear())), //added W because widening types to include number + rte.map(({user, thisYear}) => `${user.name}${thisYear}`) + +) + +// I checked the solution for the above exercise because I was unable to figure out +// that "rte.fromReader" needed to precede the code accessing the TimeService dependency. diff --git a/src/exo6 - ReaderTaskEither/exo6.solution.ts b/src/exo6 - ReaderTaskEither/exo6.solution.ts index 5bf1425..9bae245 100644 --- a/src/exo6 - ReaderTaskEither/exo6.solution.ts +++ b/src/exo6 - ReaderTaskEither/exo6.solution.ts @@ -102,13 +102,13 @@ export const getConcatenationOfTheTwoUserNamesUsingAp = ({ // and bind the return value to feed the context and use this data. export const getConcatenationOfTheBestFriendNameAndUserName = ({ - userIdOne, + userId, }: { - userIdOne: string; + userId: string; }) => pipe( rte.Do, - rte.apS('userOne', User.Repository.getById(userIdOne)), + rte.apS('userOne', User.Repository.getById(userId)), rte.bind('userTwo', ({ userOne }) => User.Repository.getById(userOne.bestFriendId), ), diff --git a/src/exo6 - ReaderTaskEither/exo6.test.ts b/src/exo6 - ReaderTaskEither/exo6.test.ts index e58ba12..e34c72b 100644 --- a/src/exo6 - ReaderTaskEither/exo6.test.ts +++ b/src/exo6 - ReaderTaskEither/exo6.test.ts @@ -60,7 +60,7 @@ describe('exo6', () => { it('should return the concatenation of the two capitalized user names based on the best friend relation', async () => { const usecase = getConcatenationOfTheBestFriendNameAndUserName({ - userIdOne: '1', + userId: '1', })({ userRepository: new User.Repository.InMemoryUserRepository([ { id: '1', name: 'rob', bestFriendId: '2' }, diff --git a/src/exo7 - Collections/exo7.exercise.ts b/src/exo7 - Collections/exo7.exercise.ts index 4e4de21..02dd97e 100644 --- a/src/exo7 - Collections/exo7.exercise.ts +++ b/src/exo7 - Collections/exo7.exercise.ts @@ -1,7 +1,9 @@ // `fp-ts` training Exercise 7 // Manipulate collections with type-classes -import { unimplemented } from '../utils'; +import { number, readonlyArray, readonlyMap, readonlySet, semigroup, string } from 'fp-ts'; +import { pipe } from 'fp-ts/lib/function'; + // In this exercise, we will learn how to manipulate essential collections // such as `Set` and `Map`. @@ -41,7 +43,18 @@ export const numberArray: ReadonlyArray = [7, 42, 1337, 1, 0, 1337, 42]; // - `fp-ts` doesn't know how you want to define equality for the inner type // and requires you to provide an `Eq` instance -export const numberSet: ReadonlySet = unimplemented(); +// const eqNumber: Eq = { +// equals: (x: number, y: number) => x === y +// } +// export const numberSet = +// readonlySet.fromReadonlyArray(eqNumber)(numberArray); + +export const numberSet: ReadonlySet = + pipe( + numberArray, + readonlySet.fromReadonlyArray(number.Eq) + ) + // Convert `numberSet` back to an array in `numberArrayFromSet`. // You need to use the `ReadonlySet` module from `fp-ts` instead of the @@ -57,7 +70,11 @@ export const numberSet: ReadonlySet = unimplemented(); // the values to be ordered in the output array, by providing an `Ord` // instance. -export const numberArrayFromSet: ReadonlyArray = unimplemented(); +export const numberArrayFromSet: ReadonlyArray = + pipe ( + numberSet, + readonlySet.toReadonlyArray(number.Ord) + ) /////////////////////////////////////////////////////////////////////////////// // MAP // @@ -95,13 +112,22 @@ export const associativeArray: ReadonlyArray<[number, string]> = [ // - You need to provide an `Eq` instance for the key type // - You need to provide a `Magma` instance for the value type. In this case, // the `Magma` instance should ignore the first value and return the second. -// (You can define your own, or look into the `Magma` or `Semigroup` module) +// (You can define your own, or look into the `Magma` or `Semigroup` (THIS ONE) module) // - You need to provide the `Foldable` instance for the input container type. // Just know that you can construct a `Map` from other types than `Array` as // long as they implement `Foldable`. Here, you can simply pass the standard // `readonlyArray.Foldable` instance. -export const mapWithLastEntry: ReadonlyMap = unimplemented(); + +///// I looked at the solution to understand how to use semigroup.last() +// Note to self: the last argument (readonlyArray.Foldable) types the input (associativeArray) + +export const mapWithLastEntry: ReadonlyMap = + pipe ( + associativeArray, + readonlyMap.fromFoldable(number.Eq, semigroup.last(), readonlyArray.Foldable) + ) + // Same thing as above, except that upon key collision we don't want to simply // select the newest entry value but append it to the previous one. @@ -122,7 +148,12 @@ export const mapWithLastEntry: ReadonlyMap = unimplemented(); // helpful in defining `mapWithLastEntry`? export const mapWithConcatenatedEntries: ReadonlyMap = - unimplemented(); + pipe ( + associativeArray, + readonlyMap.fromFoldable(number.Eq, string.Semigroup, readonlyArray.Foldable) + ) + + //Note to self: In fp-ts/lib/Semigroup, string.Semigroup is a built-in Semigroup for strings that concatenates them /////////////////////////////////////////////////////////////////////////////// // DIFFERENCE / UNION / INTERSECTION // @@ -136,13 +167,36 @@ export const odds = new Set([1, 3, 5, 7, 9]); // // HINT: // - Be mindful of the order of operands for the operator you will choose. +// const isInPrimesSet = (entry: number) => { +// return primes.has(entry) +// } + +// export const nonPrimeOdds: ReadonlySet = +// pipe ( +// odds, +// readonlySet.filter(entry => !isInPrimesSet(entry)) +// ); -export const nonPrimeOdds: ReadonlySet = unimplemented(); +export const nonPrimeOdds: ReadonlySet = + pipe ( + odds, + readonlySet.difference(number.Eq)(primes) + ); // Construct the set `primeOdds` from the two sets defined above. It should // only include the odd numbers that are also prime. -export const primeOdds: ReadonlySet = unimplemented(); +// export const primeOdds: ReadonlySet = +// pipe ( +// odds, +// readonlySet.filter(entry => isInPrimesSet(entry)) +// ); + +export const primeOdds: ReadonlySet = + pipe ( + primes, + readonlySet.intersection(number.Eq)(odds) + ); /////////////////////////////////////////////////////////////////////////////// @@ -178,10 +232,25 @@ export const pageViewsB = new Map( // // In case a page appears in both sources, their view count should be summed. -export const allPageViews: ReadonlyMap = unimplemented(); + +////// I needed to look at the solution for this part. I don't feel like I had the tools to figure this out on my own. +const S = semigroup.struct({ + page: semigroup.first(), + views: number.SemigroupSum, +}); + +export const allPageViews: ReadonlyMap = +pipe( + pageViewsA, + readonlyMap.union(string.Eq, S)(pageViewsB) +) + // Construct the `Map` with the total page views but only for the pages that // appear in both sources of analytics `pageViewsA` and `pageViewsB`. export const intersectionPageViews: ReadonlyMap = - unimplemented(); + pipe( + pageViewsA, + readonlyMap.intersection(string.Eq, S)(pageViewsB) +) diff --git a/src/exo8 - Own combinators/exo8.exercise.ts b/src/exo8 - Own combinators/exo8.exercise.ts index cec7405..d45c608 100644 --- a/src/exo8 - Own combinators/exo8.exercise.ts +++ b/src/exo8 - Own combinators/exo8.exercise.ts @@ -3,7 +3,8 @@ import { Either } from 'fp-ts/Either'; import { ReaderTaskEither } from 'fp-ts/ReaderTaskEither'; -import { unimplemented } from '../utils'; +import { rte } from '../readerTaskEither'; +import { Reader } from 'fp-ts/lib/Reader'; // Technically, a combinator is a pure function with no free variables in it, // i.e. one that does not depend on any variable from its enclosing scope. @@ -66,9 +67,6 @@ import { unimplemented } from '../utils'; // Well there you have it, `bindEitherK` is nothing more than // `rte.bind(name, a => rte.fromEither(f(a)))` -const unimplementedBindCombinator = (_name: any, _f: (...props: any) => any) => - unimplemented; -const unimplementedApSCombinator = (_name: any, _thing: any) => unimplemented; export const bindEitherK: ( name: Exclude, @@ -79,13 +77,31 @@ export const bindEitherK: ( R, E, { readonly [K in N | keyof A]: K extends keyof A ? A[K] : B } -> = unimplemented; +> = (name, f) => rte.bind( + name, + a => (rte.fromEither(f(a))) +) + +// const myFn: (arg: string) => (x: boolean) => () => boolean = (arg) => { +// return () => arg === '' +// }; // Write the implementation and type definition of `bindEitherKW`, the // "Widened" version of `bindEitherK`. -export const bindEitherKW = unimplementedBindCombinator; - +export const bindEitherKW: ( + name: Exclude, + f: (a: A) => Either, +) => ( + ma: ReaderTaskEither, +) => ReaderTaskEither< + R, + E2 | E1, + { readonly [K in N | keyof A]: K extends keyof A ? A[K] : B } +> = (name, f) => rte.bindW( + name, + a => (rte.fromEither(f(a))) +) // Write the implementations and type definitions of `apSEitherK` and // `apSEitherKW`. // @@ -93,10 +109,33 @@ export const bindEitherKW = unimplementedBindCombinator; // - remember that "widen" in the case of `Either` means the union of the // possible error types -export const apSEitherK = unimplementedApSCombinator; - -export const apSEitherKW = unimplementedApSCombinator; +export const apSEitherK: ( + name: Exclude, + fb: Either, +) => ( + fa: ReaderTaskEither, +) => ReaderTaskEither< + R, + E, + { readonly [K in N | keyof A]: K extends keyof A ? A[K] : B } +> = (name, fb) => rte.apS( + name, + rte.fromEither(fb) +) +export const apSEitherKW: ( + name: Exclude, + fb: Either, +) => ( + fa: ReaderTaskEither, +) => ReaderTaskEither< + R, + E2 | E1, + { readonly [K in N | keyof A]: K extends keyof A ? A[K] : B } +> = (name, fb) => rte.apSW( + name, + rte.fromEither(fb) +) // Write the implementations and type definitions of `bindReaderK` and // `bindReaderKW`. // @@ -104,6 +143,30 @@ export const apSEitherKW = unimplementedApSCombinator; // - remember that "widen" in the case of `Reader` means the intersection of // the possible environment types -export const bindReaderK = unimplementedBindCombinator; +export const bindReaderK: ( + name: Exclude, + f: (a: A) => Reader, +) => ( + ma: ReaderTaskEither, +) => ReaderTaskEither< + R, + E, + { readonly [K in N | keyof A]: K extends keyof A ? A[K] : A } +> = (name, f) => rte.bind( + name, + a => (rte.fromReader(f(a))) +) -export const bindReaderKW = unimplementedBindCombinator; +export const bindReaderKW: ( + name: Exclude, + f: (a: A) => Reader, +) => ( + ma: ReaderTaskEither, +) => ReaderTaskEither< + R2 & R1, + E, + { readonly [K in N | keyof A]: K extends keyof A ? A[K] : A } +> = (name, f) => rte.bindW( + name, + a => (rte.fromReader(f(a))) +)