Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lauren fp ts exercises #151

Draft
wants to merge 16 commits into
base: main
Choose a base branch
from
11 changes: 6 additions & 5 deletions src/exo0 - Composing with pipe and flow/exo0.exercise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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:
Expand All @@ -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);
35 changes: 25 additions & 10 deletions src/exo1 - Basic types/exo1.exercise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
// - 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';

export const divide = (a: number, b: number): number => {
return a / b;
Expand All @@ -25,7 +24,23 @@ export const divide = (a: number, b: number): number => {
// - `option.none`

export const safeDivide: (a: number, b: number) => Option<number> =
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.
Expand Down Expand Up @@ -54,11 +69,11 @@ export const safeDivide: (a: number, b: number) => Option<number> =
// 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<DivisionByZeroError, number> = unimplemented;
b: number) => b === 0 ? either.left(DivisionByZero) :
either.right(divide(a, b));

///////////////////////////////////////////////////////////////////////////////
// TASKEITHER //
Expand All @@ -83,7 +98,7 @@ export const asyncDivide = async (a: number, b: number) => {
// a TaskEither<Error, T>:
// - `taskEither.tryCatch(f: () => promise, onReject: reason => leftValue)`

export const asyncSafeDivideWithError: (
export const asyncSafeDivideWithError = (
a: number,
b: number,
) => TaskEither<DivisionByZeroError, number> = unimplementedAsync;
) => taskEither.tryCatch(() => asyncDivide(a, b), () => DivisionByZero)
128 changes: 108 additions & 20 deletions src/exo2 - Combinators/exo2.exercise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 //
Expand Down Expand Up @@ -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<Character>,
) => Either<NoAttackerFailure | InvalidAttackerFailure, Damage> = unimplemented;

export const checkAttackerAndBurn: (
attacker: Option<Character>,
) => Either<NoAttackerFailure | InvalidAttackerFailure, Damage> = unimplemented;

export const checkAttackerAndShoot: (
attacker: Option<Character>,
) => Either<NoAttackerFailure | InvalidAttackerFailure, Damage> = 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<Character>,
): Either<NoAttackerFailure | InvalidAttackerFailure, Damage> => pipe(
target,
isAttackerDefined,
either.flatMap(smash)
)

export const checkAttackerAndBurn = (
target: Option<Character>,
): Either<NoAttackerFailure | InvalidAttackerFailure, Damage> => pipe(
target,
isAttackerDefined,
either.flatMap(burn)
);

export const checkAttackerAndShoot = (
target: Option<Character>,
): Either<NoAttackerFailure | InvalidAttackerFailure, Damage> => pipe (
target,
isAttackerDefined,
either.flatMap(shoot)
);

///////////////////////////////////////////////////////////////////////////////
// OPTION //
Expand All @@ -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<Damage> =
unimplemented;

export const burnOption: (character: Character) => Option<Damage> =
unimplemented;

export const shootOption: (character: Character) => Option<Damage> =
unimplemented;
export const smashOption = (character: Character): Option<Damage> =>
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 //
Expand All @@ -168,11 +228,39 @@ export const shootOption: (character: Character) => Option<Damage> =
// perform mapping and filtering at the same time by applying a function
// of type `A => Option<B>` 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<Character>) => ({
[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<Character>) => TotalDamage =
unimplemented;
76 changes: 60 additions & 16 deletions src/exo3 - Sort with Ord/exo3.exercise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -34,13 +41,19 @@ import { unimplemented } from '../utils';
// expose some pre constructed instances of `Ord<T>` for said primitives such as
// `string.Ord: Ord<string>` or `number.Ord: Ord<number>`.

export const sortStrings: (
export const sortStrings = (
strings: ReadonlyArray<string>,
) => ReadonlyArray<string> = unimplemented;
): ReadonlyArray<string> => pipe(
strings,
readonlyArray.sort(string.Ord)
);

export const sortNumbers: (
export const sortNumbers = (
numbers: ReadonlyArray<number>,
) => ReadonlyArray<number> = unimplemented;
): ReadonlyArray<number> => pipe (
numbers,
readonlyArray.sort(number.Ord)
);

///////////////////////////////////////////////////////////////////////////////
// REVERSE SORT //
Expand All @@ -55,9 +68,12 @@ export const sortNumbers: (
//
// HINT: Any ordering can be reversed with a simple function `ord.reverse`.

export const sortNumbersDescending: (
export const sortNumbersDescending = (
numbers: ReadonlyArray<number>,
) => ReadonlyArray<number> = unimplemented;
): ReadonlyArray<number> => pipe (
numbers,
readonlyArray.sort(ord.reverse(number.Ord))
);

///////////////////////////////////////////////////////////////////////////////
// SORT OPTIONAL VALUES //
Expand All @@ -73,9 +89,12 @@ 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<Option<number>>,
) => ReadonlyArray<Option<number>> = unimplemented;
): ReadonlyArray<Option<number>> => pipe (
optionalNumbers,
readonlyArray.sort(option.getOrd(number.Ord))
);

///////////////////////////////////////////////////////////////////////////////
// SORT COMPLEX OBJECTS //
Expand All @@ -99,13 +118,25 @@ export interface Person {
readonly age: Option<number>;
}

export const sortPersonsByName: (
export const sortPersonsByName = (
persons: ReadonlyArray<Person>,
) => ReadonlyArray<Person> = unimplemented;

export const sortPersonsByAge: (
): ReadonlyArray<Person> => pipe (
persons,
readonlyArray.sortBy([pipe(
string.Ord,
ord.contramap((person: Person) => person.name)
)])
);

export const sortPersonsByAge = (
persons: ReadonlyArray<Person>,
) => ReadonlyArray<Person> = unimplemented;
): ReadonlyArray<Person> => pipe (
persons,
readonlyArray.sortBy( [pipe (
option.getOrd(number.Ord),
ord.contramap((person: Person) => person.age)
)])
);

///////////////////////////////////////////////////////////////////////////////
// COMBINE SORTING SCHEMES //
Expand All @@ -116,6 +147,19 @@ 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<Person>,
) => ReadonlyArray<Person> = unimplemented;
): ReadonlyArray<Person> => pipe (
persons,
readonlyArray.sortBy([byAge, byName])
);
Loading