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.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 = value => flow(isEven, not)(value);

// 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) => flow(next, next, next)(value);
35 changes: 25 additions & 10 deletions src/exo1 - Basic types/exo1.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));
lauren-inato marked this conversation as resolved.
Show resolved Hide resolved

///////////////////////////////////////////////////////////////////////////////
// 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)
122 changes: 105 additions & 17 deletions src/exo2 - Combinators/exo2.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 invalidTargetFailure = Failure.builder(
// common operations done with the `Either` type and it is available through
// the `flatMap` operator.

export const checkTargetAndSmash: (


//****** I looked at the solution to get help on this one. **********//

const isTargetDefined =
either.fromOption(() =>
noTargetFailure("No unit currently selected"));

const isAllowedWarriorAction =
either.fromPredicate(isWarrior, target =>
invalidTargetFailure(`${target.toString()} cannot perform smash`));

const isAllowedWizardAction =
either.fromPredicate(isWizard, target =>
invalidTargetFailure(`${target.toString()} cannot perform burn`));

const isAllowedArcherAction =
either.fromPredicate(isArcher, target =>
invalidTargetFailure(`${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 checkTargetAndSmash = (
target: Option<Character>,
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
): Either<NoTargetFailure | InvalidTargetFailure, Damage> => pipe(
target,
isTargetDefined,
either.chainW(smash)
)

export const checkTargetAndBurn: (
export const checkTargetAndBurn = (
target: Option<Character>,
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
): Either<NoTargetFailure | InvalidTargetFailure, Damage> => pipe(
target,
isTargetDefined,
either.chainW(burn)
);

export const checkTargetAndShoot: (
export const checkTargetAndShoot = (
target: Option<Character>,
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
): Either<NoTargetFailure | InvalidTargetFailure, Damage> => pipe (
target,
isTargetDefined,
either.chainW(shoot)
);

///////////////////////////////////////////////////////////////////////////////
// OPTION //
Expand All @@ -146,14 +194,26 @@ 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<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))),
lauren-inato marked this conversation as resolved.
Show resolved Hide resolved
*/

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;