From 53440267bddc7a25dcc89c0982d63937caf766c9 Mon Sep 17 00:00:00 2001 From: Hugo Saracino Date: Tue, 11 Jan 2022 14:48:20 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Switch=20to=20the=20preferred=20?= =?UTF-8?q?way=20of=20importing=20fp-ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Failure.ts | 16 ++------- src/exo1/exo1.test.ts | 28 ++++++++-------- src/exo1/exo1.ts | 29 ++++++++--------- src/exo2/exo2.test.ts | 76 +++++++++++++++++++++---------------------- src/exo2/exo2.ts | 52 +++++++++++------------------ src/exo3/exo3.test.ts | 29 ++++++++--------- src/exo3/exo3.ts | 34 +++++++++---------- 7 files changed, 116 insertions(+), 148 deletions(-) diff --git a/src/Failure.ts b/src/Failure.ts index 29c8b8b..0899144 100644 --- a/src/Failure.ts +++ b/src/Failure.ts @@ -1,8 +1,5 @@ import * as decod from 'decod'; -import * as Either from 'fp-ts/lib/Either'; -import * as TaskEither from 'fp-ts/lib/TaskEither'; -import * as Task from 'fp-ts/lib/Task'; -import { pipe } from 'fp-ts/lib/pipeable'; +import { either, task } from 'fp-ts'; const decodeErrorMessage = decod.at('message', decod.string); @@ -70,16 +67,9 @@ export class Failure { throw failure.toError(); }; - static eitherUnsafeGet = (either: Either.Either): A => - pipe(either, Either.getOrElseW(Failure.throw)); + static eitherUnsafeGet = either.getOrElseW(Failure.throw); - static taskEitherUnsafeGet = ( - taskEither: TaskEither.TaskEither, - ): Task.Task => - pipe( - taskEither, - TaskEither.getOrElseW((failure) => async () => Failure.throw(failure)), - ); + static taskEitherUnsafeGet = task.map(this.eitherUnsafeGet); } export type FailureTypes> = F[keyof F]; diff --git a/src/exo1/exo1.test.ts b/src/exo1/exo1.test.ts index 08b9a83..d7f1e8e 100644 --- a/src/exo1/exo1.test.ts +++ b/src/exo1/exo1.test.ts @@ -1,6 +1,4 @@ -import * as Either from 'fp-ts/lib/Either'; -import * as Option from 'fp-ts/lib/Option'; - +import { either, option } from 'fp-ts'; import { divide, DivisionByZero, @@ -24,26 +22,26 @@ describe('exo1', () => { describe('safeDivide', () => { it('should return the result of dividing two numbers', () => { - expect(safeDivide(25, 5)).toStrictEqual(Option.some(5)); + expect(safeDivide(25, 5)).toStrictEqual(option.some(5)); }); - it('should return Option.none if the denominator is zero', () => { - expect(safeDivide(25, 0)).toStrictEqual(Option.none); - expect(safeDivide(-25, 0)).toStrictEqual(Option.none); + it('should return option.none if the denominator is zero', () => { + expect(safeDivide(25, 0)).toStrictEqual(option.none); + expect(safeDivide(-25, 0)).toStrictEqual(option.none); }); }); describe('safeDivideWithError', () => { it('should return the result of dividing two numbers', () => { - expect(safeDivideWithError(25, 5)).toStrictEqual(Either.right(5)); + expect(safeDivideWithError(25, 5)).toStrictEqual(either.right(5)); }); - it('should return Either.left(DivisionByZero) if the denominator is zero', () => { + it('should return either.left(DivisionByZero) if the denominator is zero', () => { expect(safeDivideWithError(25, 0)).toStrictEqual( - Either.left(DivisionByZero), + either.left(DivisionByZero), ); expect(safeDivideWithError(-25, 0)).toStrictEqual( - Either.left(DivisionByZero), + either.left(DivisionByZero), ); }); }); @@ -65,15 +63,15 @@ describe('exo1', () => { it('should eventually return the result of dividing two numbers', async () => { const result = await asyncSafeDivideWithError(25, 5)(); - expect(result).toStrictEqual(Either.right(5)); + expect(result).toStrictEqual(either.right(5)); }); - it('should eventually return Either.left(DivisionByZero) if the denominator is zero', async () => { + it('should eventually return either.left(DivisionByZero) if the denominator is zero', async () => { const resultA = await asyncSafeDivideWithError(25, 0)(); const resultB = await asyncSafeDivideWithError(-25, 0)(); - expect(resultA).toStrictEqual(Either.left(DivisionByZero)); - expect(resultB).toStrictEqual(Either.left(DivisionByZero)); + expect(resultA).toStrictEqual(either.left(DivisionByZero)); + expect(resultB).toStrictEqual(either.left(DivisionByZero)); }); }); }); diff --git a/src/exo1/exo1.ts b/src/exo1/exo1.ts index 02c5707..c79e7d6 100644 --- a/src/exo1/exo1.ts +++ b/src/exo1/exo1.ts @@ -4,10 +4,9 @@ // - Either // - TaskEither -import * as Option from 'fp-ts/lib/Option'; -import * as Either from 'fp-ts/lib/Either'; -import * as TaskEither from 'fp-ts/lib/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'; export const divide = (a: number, b: number): number => { @@ -22,13 +21,11 @@ export const divide = (a: number, b: number): number => { // safeDivide : (a: number, b: number) => Option // // HINT: Option has two basic contructors: -// - `Option.some(value)` -// - `Option.none` +// - `option.some(value)` +// - `option.none` -export const safeDivide: ( - a: number, - b: number, -) => Option.Option = unimplemented; +export const safeDivide: (a: number, b: number) => Option = + unimplemented; /////////////////////////////////////////////////////////////////////////////// // EITHER // @@ -40,10 +37,10 @@ export const safeDivide: ( // BONUS POINT: Implement `safeDivideWithError` in terms of `safeDivide`. // // HINT : Either has two basic constructors: -// - `Either.left(leftValue)` -// - `Either.right(rightValue)` +// - `either.left(leftValue)` +// - `either.right(rightValue)` // as well as "smarter" constructors like: -// - `Either.fromOption(() => leftValue)(option)` +// - `either.fromOption(() => leftValue)(option)` // Here is an simple error type to help you: export type DivisionByZeroError = 'Error: Division by zero'; @@ -52,7 +49,7 @@ export const DivisionByZero = 'Error: Division by zero' as const; export const safeDivideWithError: ( a: number, b: number, -) => Either.Either = unimplemented; +) => Either = unimplemented; /////////////////////////////////////////////////////////////////////////////// // TASKEITHER // @@ -75,9 +72,9 @@ export const asyncDivide = async (a: number, b: number) => { // // HINT: TaskEither has a special constructor to transform a Promise into // a TaskEither: -// - `TaskEither.tryCatch(f: () => promise, onReject: reason => leftValue)` +// - `taskEither.tryCatch(f: () => promise, onReject: reason => leftValue)` export const asyncSafeDivideWithError: ( a: number, b: number, -) => TaskEither.TaskEither = unimplementedAsync; +) => TaskEither = unimplementedAsync; diff --git a/src/exo2/exo2.test.ts b/src/exo2/exo2.test.ts index ad02679..7385175 100644 --- a/src/exo2/exo2.test.ts +++ b/src/exo2/exo2.test.ts @@ -1,6 +1,4 @@ -import * as Either from 'fp-ts/lib/Either'; -import * as Option from 'fp-ts/lib/Option'; - +import { either, option } from 'fp-ts'; import { Warrior, Wizard, @@ -20,8 +18,8 @@ import { describe('exo2', () => { describe('checkTargetAndSmash', () => { it('should return a NoTarget error if no unit is selected', () => { - const result = checkTargetAndSmash(Option.none); - const expected = Either.left( + const result = checkTargetAndSmash(option.none); + const expected = either.left( noTargetFailure('No unit currently selected'), ); @@ -30,14 +28,14 @@ describe('exo2', () => { it('should return an InvalidTarget error if the wrong unit is selected', () => { const archer = new Archer(); - const resultArcher = checkTargetAndSmash(Option.some(archer)); - const expectedArcher = Either.left( + const resultArcher = checkTargetAndSmash(option.some(archer)); + const expectedArcher = either.left( invalidTargetFailure('Archer cannot perform smash'), ); const wizard = new Wizard(); - const resultWizard = checkTargetAndSmash(Option.some(wizard)); - const expectedWizard = Either.left( + const resultWizard = checkTargetAndSmash(option.some(wizard)); + const expectedWizard = either.left( invalidTargetFailure('Wizard cannot perform smash'), ); @@ -48,8 +46,8 @@ describe('exo2', () => { it('should return the proper type of damage', () => { const warrior = new Warrior(); - const result = checkTargetAndSmash(Option.some(warrior)); - const expected = Either.right(Damage.Physical); + const result = checkTargetAndSmash(option.some(warrior)); + const expected = either.right(Damage.Physical); expect(result).toStrictEqual(expected); }); @@ -57,8 +55,8 @@ describe('exo2', () => { describe('checkTargetAndBurn', () => { it('should return a NoTarget error if no unit is selected', () => { - const result = checkTargetAndBurn(Option.none); - const expected = Either.left( + const result = checkTargetAndBurn(option.none); + const expected = either.left( noTargetFailure('No unit currently selected'), ); @@ -67,14 +65,14 @@ describe('exo2', () => { it('should return an InvalidTarget error if the wrong unit is selected', () => { const warrior = new Warrior(); - const resultWarrior = checkTargetAndBurn(Option.some(warrior)); - const expectedWarrior = Either.left( + const resultWarrior = checkTargetAndBurn(option.some(warrior)); + const expectedWarrior = either.left( invalidTargetFailure('Warrior cannot perform burn'), ); const archer = new Archer(); - const resultArcher = checkTargetAndBurn(Option.some(archer)); - const expectedArcher = Either.left( + const resultArcher = checkTargetAndBurn(option.some(archer)); + const expectedArcher = either.left( invalidTargetFailure('Archer cannot perform burn'), ); @@ -85,8 +83,8 @@ describe('exo2', () => { it('should return the proper type of damage', () => { const wizard = new Wizard(); - const result = checkTargetAndBurn(Option.some(wizard)); - const expected = Either.right(Damage.Magical); + const result = checkTargetAndBurn(option.some(wizard)); + const expected = either.right(Damage.Magical); expect(result).toStrictEqual(expected); }); @@ -94,8 +92,8 @@ describe('exo2', () => { describe('checkTargetAndShoot', () => { it('should return a NoTarget error if no unit is selected', () => { - const result = checkTargetAndShoot(Option.none); - const expected = Either.left( + const result = checkTargetAndShoot(option.none); + const expected = either.left( noTargetFailure('No unit currently selected'), ); @@ -104,14 +102,14 @@ describe('exo2', () => { it('should return an InvalidTarget error if the wrong unit is selected', () => { const warrior = new Warrior(); - const resultWarrior = checkTargetAndShoot(Option.some(warrior)); - const expectedWarrior = Either.left( + const resultWarrior = checkTargetAndShoot(option.some(warrior)); + const expectedWarrior = either.left( invalidTargetFailure('Warrior cannot perform shoot'), ); const wizard = new Wizard(); - const resultWizard = checkTargetAndShoot(Option.some(wizard)); - const expectedWizard = Either.left( + const resultWizard = checkTargetAndShoot(option.some(wizard)); + const expectedWizard = either.left( invalidTargetFailure('Wizard cannot perform shoot'), ); @@ -122,80 +120,80 @@ describe('exo2', () => { it('should return the proper type of damage', () => { const archer = new Archer(); - const result = checkTargetAndShoot(Option.some(archer)); - const expected = Either.right(Damage.Ranged); + const result = checkTargetAndShoot(option.some(archer)); + const expected = either.right(Damage.Ranged); expect(result).toStrictEqual(expected); }); }); describe('smashOption', () => { - it('should return Option.none if the character is of the wrong type', () => { + it('should return option.none if the character is of the wrong type', () => { const wizard = new Wizard(); const archer = new Archer(); const resultWizard = smashOption(wizard); const resultArcher = smashOption(archer); - const expected = Option.none; + const expected = option.none; expect(resultWizard).toStrictEqual(expected); expect(resultArcher).toStrictEqual(expected); }); - it('should return Option.some(Damage.Physical) if the character is a warrior', () => { + it('should return option.some(Damage.Physical) if the character is a warrior', () => { const warrior = new Warrior(); const result = smashOption(warrior); - const expected = Option.some(Damage.Physical); + const expected = option.some(Damage.Physical); expect(result).toStrictEqual(expected); }); }); describe('burnOption', () => { - it('should return Option.none if the character is of the wrong type', () => { + it('should return option.none if the character is of the wrong type', () => { const warrior = new Warrior(); const archer = new Archer(); const resultWarrior = burnOption(warrior); const resultArcher = burnOption(archer); - const expected = Option.none; + const expected = option.none; expect(resultWarrior).toStrictEqual(expected); expect(resultArcher).toStrictEqual(expected); }); - it('should return Option.some(Damage.Magical) if the character is a wizard', () => { + it('should return option.some(Damage.Magical) if the character is a wizard', () => { const wizard = new Wizard(); const result = burnOption(wizard); - const expected = Option.some(Damage.Magical); + const expected = option.some(Damage.Magical); expect(result).toStrictEqual(expected); }); }); describe('shootOption', () => { - it('should return Option.none if the character is of the wrong type', () => { + it('should return option.none if the character is of the wrong type', () => { const warrior = new Warrior(); const wizard = new Wizard(); const resultWizard = shootOption(wizard); const resultWarrior = shootOption(warrior); - const expected = Option.none; + const expected = option.none; expect(resultWarrior).toStrictEqual(expected); expect(resultWizard).toStrictEqual(expected); }); - it('should return Option.some(Damage.Ranged) if the character is an archer', () => { + it('should return option.some(Damage.Ranged) if the character is an archer', () => { const archer = new Archer(); const result = shootOption(archer); - const expected = Option.some(Damage.Ranged); + const expected = option.some(Damage.Ranged); expect(result).toStrictEqual(expected); }); diff --git a/src/exo2/exo2.ts b/src/exo2/exo2.ts index e7eecfb..ffb7842 100644 --- a/src/exo2/exo2.ts +++ b/src/exo2/exo2.ts @@ -1,9 +1,8 @@ // `fp-ts` training Exercice 2 // Let's have fun with combinators! -import * as Option from 'fp-ts/lib/Option'; -import * as Either from 'fp-ts/lib/Either'; - +import { Either } from 'fp-ts/Either'; +import { Option } from 'fp-ts/Option'; import { Failure } from '../Failure'; import { unimplemented } from '../utils'; @@ -100,12 +99,12 @@ export const invalidTargetFailure = Failure.builder( // return the expected damage type if appropriate. // // If no unit is selected, it should return -// `Either.left(noTargetFailure('No unit currently selected'))` +// `either.left(noTargetFailure('No unit currently selected'))` // // If a unit of the wrong type is selected, it should return -// `Either.left(invalidTargetFailure(' cannot perform '))` +// `either.left(invalidTargetFailure(' cannot perform '))` // -// Otherwise, it should return `Either.right()` +// Otherwise, it should return `either.right()` // // HINT: These functions represent the public API. But it is heavily // recommended to break those down into smaller private functions that can be @@ -119,25 +118,16 @@ export const invalidTargetFailure = Failure.builder( // the `chain` operator and its slightly relaxed variant `chainW`. export const checkTargetAndSmash: ( - target: Option.Option, -) => Either.Either< - NoTargetFailure | InvalidTargetFailure, - Damage -> = unimplemented; + target: Option, +) => Either = unimplemented; export const checkTargetAndBurn: ( - target: Option.Option, -) => Either.Either< - NoTargetFailure | InvalidTargetFailure, - Damage -> = unimplemented; + target: Option, +) => Either = unimplemented; export const checkTargetAndShoot: ( - target: Option.Option, -) => Either.Either< - NoTargetFailure | InvalidTargetFailure, - Damage -> = unimplemented; + target: Option, +) => Either = unimplemented; /////////////////////////////////////////////////////////////////////////////// // OPTION // @@ -156,17 +146,14 @@ export const checkTargetAndShoot: ( // 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.Option = unimplemented; +export const smashOption: (character: Character) => Option = + unimplemented; -export const burnOption: ( - character: Character, -) => Option.Option = unimplemented; +export const burnOption: (character: Character) => Option = + unimplemented; -export const shootOption: ( - character: Character, -) => Option.Option = unimplemented; +export const shootOption: (character: Character) => Option = + unimplemented; /////////////////////////////////////////////////////////////////////////////// // ARRAY // @@ -187,6 +174,5 @@ export interface TotalDamage { [Damage.Ranged]: number; } -export const attack: ( - army: ReadonlyArray, -) => TotalDamage = unimplemented; +export const attack: (army: ReadonlyArray) => TotalDamage = + unimplemented; diff --git a/src/exo3/exo3.test.ts b/src/exo3/exo3.test.ts index ecc2375..c4a6904 100644 --- a/src/exo3/exo3.test.ts +++ b/src/exo3/exo3.test.ts @@ -1,5 +1,4 @@ -import * as Option from 'fp-ts/lib/Option'; - +import { option } from 'fp-ts'; import { sortStrings, sortNumbers, @@ -46,10 +45,10 @@ describe('exo3', () => { describe('sortOptionalNumbers', () => { it('should return a sorted array of optional numbers', () => { - const optionalNumbers = [Option.some(1337), Option.none, Option.some(42)]; + const optionalNumbers = [option.some(1337), option.none, option.some(42)]; const result = sortOptionalNumbers(optionalNumbers); - const expected = [Option.none, Option.some(42), Option.some(1337)]; + const expected = [option.none, option.some(42), option.some(1337)]; expect(result).toStrictEqual(expected); }); @@ -57,9 +56,9 @@ describe('exo3', () => { describe('sortPersonsByName', () => { it('should return an array of persons alphabetically sorted by their name', () => { - const alice = { name: 'Alice', age: Option.none }; - const bob = { name: 'Bob', age: Option.none }; - const crystal = { name: 'Crystal', age: Option.none }; + const alice = { name: 'Alice', age: option.none }; + const bob = { name: 'Bob', age: option.none }; + const crystal = { name: 'Crystal', age: option.none }; const persons = [crystal, alice, bob]; @@ -72,9 +71,9 @@ describe('exo3', () => { describe('sortPersonsByName', () => { it('should return an array of persons sorted by their age', () => { - const alice = { name: 'Alice', age: Option.some(42) }; - const bob = { name: 'Bob', age: Option.none }; - const crystal = { name: 'Crystal', age: Option.some(29) }; + const alice = { name: 'Alice', age: option.some(42) }; + const bob = { name: 'Bob', age: option.none }; + const crystal = { name: 'Crystal', age: option.some(29) }; const persons = [crystal, alice, bob]; @@ -87,11 +86,11 @@ describe('exo3', () => { describe('sortPersonsByName', () => { it('should return an array of persons sorted first by age and then by name', () => { - const alice = { name: 'Alice', age: Option.some(42) }; - const bob = { name: 'Bob', age: Option.none }; - const crystal = { name: 'Crystal', age: Option.some(29) }; - const dorian = { name: 'Dorian', age: Option.some(29) }; - const edgar = { name: 'Edgar', age: Option.none }; + const alice = { name: 'Alice', age: option.some(42) }; + const bob = { name: 'Bob', age: option.none }; + const crystal = { name: 'Crystal', age: option.some(29) }; + const dorian = { name: 'Dorian', age: option.some(29) }; + const edgar = { name: 'Edgar', age: option.none }; const persons = [dorian, alice, edgar, bob, crystal]; diff --git a/src/exo3/exo3.ts b/src/exo3/exo3.ts index edc9db0..3d38b82 100644 --- a/src/exo3/exo3.ts +++ b/src/exo3/exo3.ts @@ -1,8 +1,7 @@ // `fp-ts` training Exercice 3 // Sort things out with `Ord` -import * as Option from 'fp-ts/lib/Option'; - +import { Option } from 'fp-ts/Option'; import { unimplemented } from '../utils'; // Have you ever looked at the methods provided by `fp-ts` own `Array` and @@ -13,7 +12,7 @@ import { unimplemented } from '../utils'; // The difference with JavaScript's native `Array.prototype` methods is that // these are more `fp-ts` friendly. // -// In the following exercice, we will take a look at `Array.sort`. Contrary to +// In the following exercice, we will take a look at `array.sort`. Contrary to // its JavaScript counterpart, `fp-ts` sort takes as an argument something of // type `Ord` where `T` is the type of elements contained in the collection. // @@ -27,13 +26,13 @@ import { unimplemented } from '../utils'; // like `string` or `number` and return a new array with those values but // sorted. // -// Obviously, we want to call `ReadonlyArray.sort` (the `fp-ts` version! no +// Obviously, we want to call `readonlyArray.sort` (the `fp-ts` version! no // cheating). But, contrary to `ReadonlyArray.prototype.sort` which takes an // ordering function, this sort will only accept an `Ord`. // -// HINT: The `Ord` module from `fp-ts` exposes some preconstructed instances -// of `Ord` for a few primitive `T`s such as `ordString: Ord` or -// `ordNumber: Ord`. +// HINT: The primitive type modules from `fp-ts` (`number`, `string`...) +// expose some preconstructed instances of `Ord` for said primitives such as +// `string.Ord: Ord` or `number.Ord: Ord`. export const sortStrings: ( strings: ReadonlyArray, @@ -48,12 +47,13 @@ export const sortNumbers: ( /////////////////////////////////////////////////////////////////////////////// // This next function will sort an array of numbers but in descending order -// (which unfortunately is the reverse ordering from the provided `ordNumber`). +// (which unfortunately is the reverse ordering from the one provided by +// `number.Ord`). // // Sure, we could just use `sortNumbers` defined earlier and then reverse the // whole array but that would be horribly inefficient wouldn't it? // -// HINT: Any ordering can be reversed with a simple function `Ord.getDualOrd`. +// HINT: Any ordering can be reversed with a simple function `ord.reverse`. export const sortNumbersDescending: ( numbers: ReadonlyArray, @@ -64,18 +64,18 @@ export const sortNumbersDescending: ( /////////////////////////////////////////////////////////////////////////////// // This next function will sort an array of numbers wrapped in `Option` with -// the following constraint: `Option.none` < `Option.some(_)`. +// the following constraint: `option.none` < `option.some(_)`. // -// As such, we cannot simply use `ordNumber` because it has type `Ord` +// As such, we cannot simply use `number.Ord` because it has type `Ord` // and we need an instance of `Ord>`. // // HINT: Some of `fp-ts` wrapper types such as `Option` do already have a way // of building an `Ord` instance for their qualified inner type. You may want -// to take a look at `Option.getOrd`. +// to take a look at `option.getOrd`. export const sortOptionalNumbers: ( - optionalNumbers: ReadonlyArray>, -) => ReadonlyArray> = unimplemented; + optionalNumbers: ReadonlyArray>, +) => ReadonlyArray> = unimplemented; /////////////////////////////////////////////////////////////////////////////// // SORT COMPLEX OBJECTS // @@ -92,11 +92,11 @@ export const sortOptionalNumbers: ( // // HINT: You can build an instance of `Ord` specialized for a field for a // record with many fields by declaring how to access that field and which -// primitive `Ord` instance to use. This can be achieved with `Ord.contramap`. +// primitive `Ord` instance to use. This can be achieved with `ord.contramap`. export interface Person { readonly name: string; - readonly age: Option.Option; + readonly age: Option; } export const sortPersonsByName: ( @@ -114,7 +114,7 @@ export const sortPersonsByAge: ( // Now, we want to sort the array first by age, but for people of the same age, // we want to sort them by name. // -// HINT: Take a look at `ReadonlyArray.sortBy` +// HINT: Take a look at `readonlyArray.sortBy` export const sortPersonsByAgeThenByName: ( person: ReadonlyArray,