🎨 Switch to the preferred way of importing fp-ts

This commit is contained in:
Hugo Saracino 2022-01-11 14:48:20 +01:00
parent 9102b3da54
commit 53440267bd
7 changed files with 116 additions and 148 deletions

View file

@ -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<T extends string = FailureType> {
throw failure.toError();
};
static eitherUnsafeGet = <A>(either: Either.Either<Failure, A>): A =>
pipe(either, Either.getOrElseW(Failure.throw));
static eitherUnsafeGet = either.getOrElseW(Failure.throw);
static taskEitherUnsafeGet = <A>(
taskEither: TaskEither.TaskEither<Failure, A>,
): Task.Task<A> =>
pipe(
taskEither,
TaskEither.getOrElseW((failure) => async () => Failure.throw(failure)),
);
static taskEitherUnsafeGet = task.map(this.eitherUnsafeGet);
}
export type FailureTypes<F extends Record<string, string>> = F[keyof F];

View file

@ -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));
});
});
});

View file

@ -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<number>
//
// 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<number> = unimplemented;
export const safeDivide: (a: number, b: number) => Option<number> =
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<DivisionByZeroError, number> = unimplemented;
) => Either<DivisionByZeroError, number> = unimplemented;
///////////////////////////////////////////////////////////////////////////////
// TASKEITHER //
@ -75,9 +72,9 @@ export const asyncDivide = async (a: number, b: number) => {
//
// HINT: TaskEither has a special constructor to transform a Promise<T> into
// a TaskEither<Error, T>:
// - `TaskEither.tryCatch(f: () => promise, onReject: reason => leftValue)`
// - `taskEither.tryCatch(f: () => promise, onReject: reason => leftValue)`
export const asyncSafeDivideWithError: (
a: number,
b: number,
) => TaskEither.TaskEither<DivisionByZeroError, number> = unimplementedAsync;
) => TaskEither<DivisionByZeroError, number> = unimplementedAsync;

View file

@ -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);
});

View file

@ -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('<unit_type> cannot perform <action>'))`
// `either.left(invalidTargetFailure('<unit_type> cannot perform <action>'))`
//
// Otherwise, it should return `Either.right(<expected_damage_type>)`
// Otherwise, it should return `either.right(<expected_damage_type>)`
//
// 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<Character>,
) => Either.Either<
NoTargetFailure | InvalidTargetFailure,
Damage
> = unimplemented;
target: Option<Character>,
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
export const checkTargetAndBurn: (
target: Option.Option<Character>,
) => Either.Either<
NoTargetFailure | InvalidTargetFailure,
Damage
> = unimplemented;
target: Option<Character>,
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
export const checkTargetAndShoot: (
target: Option.Option<Character>,
) => Either.Either<
NoTargetFailure | InvalidTargetFailure,
Damage
> = unimplemented;
target: Option<Character>,
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = 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<Damage> = unimplemented;
export const smashOption: (character: Character) => Option<Damage> =
unimplemented;
export const burnOption: (
character: Character,
) => Option.Option<Damage> = unimplemented;
export const burnOption: (character: Character) => Option<Damage> =
unimplemented;
export const shootOption: (
character: Character,
) => Option.Option<Damage> = unimplemented;
export const shootOption: (character: Character) => Option<Damage> =
unimplemented;
///////////////////////////////////////////////////////////////////////////////
// ARRAY //
@ -187,6 +174,5 @@ export interface TotalDamage {
[Damage.Ranged]: number;
}
export const attack: (
army: ReadonlyArray<Character>,
) => TotalDamage = unimplemented;
export const attack: (army: ReadonlyArray<Character>) => TotalDamage =
unimplemented;

View file

@ -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];

View file

@ -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<T>` 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<T>`.
//
// HINT: The `Ord` module from `fp-ts` exposes some preconstructed instances
// of `Ord<T>` for a few primitive `T`s such as `ordString: Ord<string>` or
// `ordNumber: Ord<number>`.
// HINT: The primitive type modules from `fp-ts` (`number`, `string`...)
// expose some preconstructed instances of `Ord<T>` for said primitives such as
// `string.Ord: Ord<string>` or `number.Ord: Ord<number>`.
export const sortStrings: (
strings: ReadonlyArray<string>,
@ -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<number>,
@ -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<number>`
// As such, we cannot simply use `number.Ord` because it has type `Ord<number>`
// and we need an instance of `Ord<Option<number>>`.
//
// 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<Option.Option<number>>,
) => ReadonlyArray<Option.Option<number>> = unimplemented;
optionalNumbers: ReadonlyArray<Option<number>>,
) => ReadonlyArray<Option<number>> = 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<number>;
readonly age: Option<number>;
}
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<Person>,