do all tasks
This commit is contained in:
parent
f867fef92d
commit
250e2397f1
14 changed files with 7651 additions and 4833 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -14,3 +14,6 @@ node_modules
|
||||||
dist
|
dist
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
# direnv
|
||||||
|
/.envrc
|
||||||
|
/.direnv/
|
||||||
|
|
61
flake.lock
Normal file
61
flake.lock
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681202837,
|
||||||
|
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1685012353,
|
||||||
|
"narHash": "sha256-U3oOge4cHnav8OLGdRVhL45xoRj4Ppd+It6nPC9nNIU=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "aeb75dba965e790de427b73315d5addf91a54955",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
21
flake.nix
Normal file
21
flake.nix
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { nixpkgs, flake-utils, ... }:
|
||||||
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.default = pkgs.mkShell {
|
||||||
|
packages = with pkgs; [
|
||||||
|
nodejs-18_x
|
||||||
|
nodePackages.typescript-language-server # typescript
|
||||||
|
nodePackages.vscode-langservers-extracted # html, css, json, eslint
|
||||||
|
];
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
4476
package-lock.json
generated
Normal file
4476
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -24,7 +24,7 @@
|
||||||
// - `flow(f) === f`
|
// - `flow(f) === f`
|
||||||
// - `pipe(x, f) === f(x)`
|
// - `pipe(x, f) === f(x)`
|
||||||
|
|
||||||
import { unimplemented } from '../utils';
|
import { flow, pipe } from "fp-ts/lib/function";
|
||||||
|
|
||||||
export const isEven = (value: number) => value % 2 === 0;
|
export const isEven = (value: number) => value % 2 === 0;
|
||||||
|
|
||||||
|
@ -42,9 +42,10 @@ export const not = (value: boolean) => !value;
|
||||||
// `pipe` or `flow`), write the function `isOdd` that checks if a number is
|
// `pipe` or `flow`), write the function `isOdd` that checks if a number is
|
||||||
// odd.
|
// odd.
|
||||||
|
|
||||||
export const isOddP: (value: number) => boolean = unimplemented;
|
export const isOddP: (value: number) => boolean = (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
|
// We will write a function that for any given number, computes the next
|
||||||
// one according to the following rules:
|
// one according to the following rules:
|
||||||
|
@ -56,17 +57,27 @@ export const isOddF: (value: number) => boolean = unimplemented;
|
||||||
// Below is the functional equivalent of the control flow statement if-else.
|
// Below is the functional equivalent of the control flow statement if-else.
|
||||||
|
|
||||||
export const ifThenElse =
|
export const ifThenElse =
|
||||||
<A>(onTrue: () => A, onFalse: () => A) =>
|
<A>(onTrue: () => A, onFalse: () => A) => (condition: boolean) =>
|
||||||
(condition: boolean) =>
|
|
||||||
condition ? onTrue() : onFalse();
|
condition ? onTrue() : onFalse();
|
||||||
|
|
||||||
// Using `pipe` and `ifThenElse`, write the function that computes the next step in the Collatz
|
// Using `pipe` and `ifThenElse`, write the function that computes the next step in the Collatz
|
||||||
// sequence.
|
// sequence.
|
||||||
|
|
||||||
export const next: (value: number) => number = unimplemented;
|
const divideByTwo = (a: number): number => a / 2;
|
||||||
|
|
||||||
|
const triple = (a: number): number => a * 3;
|
||||||
|
const addOne = (a: number): number => a + 1;
|
||||||
|
|
||||||
|
const tripleAndAddOne = flow(triple, addOne);
|
||||||
|
|
||||||
|
export const next: (value: number) => number = (value) =>
|
||||||
|
ifThenElse(
|
||||||
|
() => divideByTwo(value),
|
||||||
|
() => tripleAndAddOne(value),
|
||||||
|
)(isEven(value));
|
||||||
|
|
||||||
// Using only `flow` and `next`, write the function that for any given number
|
// 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 (ie. the number
|
// a_n from the Collatz sequence, returns the number a_n+3 (ie. the number
|
||||||
// three steps ahead in the sequence).
|
// three steps ahead in the sequence).
|
||||||
|
|
||||||
export const next3: (value: number) => number = unimplemented;
|
export const next3: (value: number) => number = flow(next, next, next);
|
||||||
|
|
|
@ -4,10 +4,12 @@
|
||||||
// - Either
|
// - Either
|
||||||
// - TaskEither
|
// - TaskEither
|
||||||
|
|
||||||
import { Either } from 'fp-ts/Either';
|
import { Either } from "fp-ts/Either";
|
||||||
import { Option } from 'fp-ts/Option';
|
import { Option } from "fp-ts/Option";
|
||||||
import { TaskEither } from 'fp-ts/TaskEither';
|
import { TaskEither } from "fp-ts/TaskEither";
|
||||||
import { unimplemented, sleep, unimplementedAsync } from '../utils';
|
import { sleep } from "../utils";
|
||||||
|
import { either, option, taskEither } from "fp-ts";
|
||||||
|
import { flow, pipe } from "fp-ts/lib/function";
|
||||||
|
|
||||||
export const divide = (a: number, b: number): number => {
|
export const divide = (a: number, b: number): number => {
|
||||||
return a / b;
|
return a / b;
|
||||||
|
@ -24,8 +26,12 @@ export const divide = (a: number, b: number): number => {
|
||||||
// - `option.some(value)`
|
// - `option.some(value)`
|
||||||
// - `option.none`
|
// - `option.none`
|
||||||
|
|
||||||
export const safeDivide: (a: number, b: number) => Option<number> =
|
export const safeDivide: (a: number, b: number) => Option<number> = (a, b) =>
|
||||||
unimplemented;
|
pipe(
|
||||||
|
b,
|
||||||
|
option.fromPredicate((b) => b !== 0),
|
||||||
|
option.map((b) => divide(a, b)),
|
||||||
|
);
|
||||||
|
|
||||||
// You probably wrote `safeDivide` using `if` statements and it's perfectly valid!
|
// You probably wrote `safeDivide` using `if` statements and it's perfectly valid!
|
||||||
// There are ways to not use `if` statements.
|
// There are ways to not use `if` statements.
|
||||||
|
@ -52,13 +58,16 @@ export const safeDivide: (a: number, b: number) => Option<number> =
|
||||||
// - `either.fromOption(() => leftValue)(option)`
|
// - `either.fromOption(() => leftValue)(option)`
|
||||||
|
|
||||||
// Here is an simple error type to help you:
|
// Here is an simple error type to help you:
|
||||||
export type DivisionByZeroError = 'Error: Division by zero';
|
export type DivisionByZeroError = "Error: Division by zero";
|
||||||
export const DivisionByZero = 'Error: Division by zero' as const;
|
export const DivisionByZero = "Error: Division by zero" as const;
|
||||||
|
|
||||||
export const safeDivideWithError: (
|
export const safeDivideWithError: (
|
||||||
a: number,
|
a: number,
|
||||||
b: number,
|
b: number,
|
||||||
) => Either<DivisionByZeroError, number> = unimplemented;
|
) => Either<DivisionByZeroError, number> = flow(
|
||||||
|
safeDivide,
|
||||||
|
either.fromOption(() => DivisionByZero),
|
||||||
|
);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// TASKEITHER //
|
// TASKEITHER //
|
||||||
|
@ -70,7 +79,7 @@ export const asyncDivide = async (a: number, b: number) => {
|
||||||
await sleep(1000);
|
await sleep(1000);
|
||||||
|
|
||||||
if (b === 0) {
|
if (b === 0) {
|
||||||
throw new Error('BOOM!');
|
throw new Error("BOOM!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return a / b;
|
return a / b;
|
||||||
|
@ -86,4 +95,5 @@ export const asyncDivide = async (a: number, b: number) => {
|
||||||
export const asyncSafeDivideWithError: (
|
export const asyncSafeDivideWithError: (
|
||||||
a: number,
|
a: number,
|
||||||
b: number,
|
b: number,
|
||||||
) => TaskEither<DivisionByZeroError, number> = unimplementedAsync;
|
) => TaskEither<DivisionByZeroError, number> = (a, b) =>
|
||||||
|
taskEither.tryCatch(() => asyncDivide(a, b), () => DivisionByZero);
|
||||||
|
|
123
src/exo2/exo2.ts
123
src/exo2/exo2.ts
|
@ -1,10 +1,12 @@
|
||||||
// `fp-ts` training Exercise 2
|
// `fp-ts` training Exercise 2
|
||||||
// Let's have fun with combinators!
|
// Let's have fun with combinators!
|
||||||
|
|
||||||
import { Either } from 'fp-ts/Either';
|
import { Either } from "fp-ts/Either";
|
||||||
import { Option } from 'fp-ts/Option';
|
import { Option } from "fp-ts/Option";
|
||||||
import { Failure } from '../Failure';
|
import { Failure } from "../Failure";
|
||||||
import { unimplemented } from '../utils';
|
import { either, option, readonlyArray } from "fp-ts";
|
||||||
|
import { absurd, flow } from "fp-ts/lib/function";
|
||||||
|
import { Refinement } from "fp-ts/lib/Refinement";
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// SETUP //
|
// SETUP //
|
||||||
|
@ -20,9 +22,9 @@ export type Character = Warrior | Wizard | Archer;
|
||||||
|
|
||||||
// We have three types of `Damage`, each corresponding to a character type.
|
// We have three types of `Damage`, each corresponding to a character type.
|
||||||
export enum Damage {
|
export enum Damage {
|
||||||
Physical = 'Physical damage',
|
Physical = "Physical damage",
|
||||||
Magical = 'Magical damage',
|
Magical = "Magical damage",
|
||||||
Ranged = 'Ranged damage',
|
Ranged = "Ranged damage",
|
||||||
}
|
}
|
||||||
|
|
||||||
// A `Warrior` only can output physical damage.
|
// A `Warrior` only can output physical damage.
|
||||||
|
@ -32,7 +34,7 @@ export class Warrior {
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return 'Warrior';
|
return "Warrior";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +45,7 @@ export class Wizard {
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return 'Wizard';
|
return "Wizard";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +56,7 @@ export class Archer {
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return 'Archer';
|
return "Archer";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,8 +81,8 @@ export const isArcher = (character: Character): character is Archer => {
|
||||||
// - the player can try to perform the wrong action for a character class
|
// - the player can try to perform the wrong action for a character class
|
||||||
|
|
||||||
export enum Exo2FailureType {
|
export enum Exo2FailureType {
|
||||||
NoTarget = 'Exo2FailureType_NoTarget',
|
NoTarget = "Exo2FailureType_NoTarget",
|
||||||
InvalidTarget = 'Exo2FailureType_InvalidTarget',
|
InvalidTarget = "Exo2FailureType_InvalidTarget",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type NoTargetFailure = Failure<Exo2FailureType.NoTarget>;
|
export type NoTargetFailure = Failure<Exo2FailureType.NoTarget>;
|
||||||
|
@ -117,17 +119,67 @@ export const invalidTargetFailure = Failure.builder(
|
||||||
// common operations done with the `Either` type and it is available through
|
// common operations done with the `Either` type and it is available through
|
||||||
// the `chain` operator and its slightly relaxed variant `chainW`.
|
// the `chain` operator and its slightly relaxed variant `chainW`.
|
||||||
|
|
||||||
|
const checkTargetIsSelected: (
|
||||||
|
target: Option<Character>,
|
||||||
|
) => Either<NoTargetFailure, Character> = either.fromOption(() =>
|
||||||
|
noTargetFailure("No unit currently selected")
|
||||||
|
);
|
||||||
|
|
||||||
|
const tryPerformAction: <A extends Character, B extends A>(
|
||||||
|
predicate: Refinement<A, B>,
|
||||||
|
onAction: (target: B) => Damage,
|
||||||
|
onFailure: (target: A) => string,
|
||||||
|
) => (target: A) => Either<InvalidTargetFailure, Damage> = (
|
||||||
|
predicate,
|
||||||
|
onAction,
|
||||||
|
onFailure,
|
||||||
|
) =>
|
||||||
|
flow(
|
||||||
|
either.fromPredicate(
|
||||||
|
predicate,
|
||||||
|
flow(onFailure, invalidTargetFailure),
|
||||||
|
),
|
||||||
|
either.map(onAction),
|
||||||
|
);
|
||||||
|
|
||||||
|
const trySmash = tryPerformAction(
|
||||||
|
isWarrior,
|
||||||
|
(t) => t.smash(),
|
||||||
|
(t) => `${t.toString()} cannot perform smash`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const tryBurn = tryPerformAction(
|
||||||
|
isWizard,
|
||||||
|
(t) => t.burn(),
|
||||||
|
(t) => `${t.toString()} cannot perform burn`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const tryShoot = tryPerformAction(
|
||||||
|
isArcher,
|
||||||
|
(t) => t.shoot(),
|
||||||
|
(t) => `${t.toString()} cannot perform shoot`,
|
||||||
|
);
|
||||||
|
|
||||||
export const checkTargetAndSmash: (
|
export const checkTargetAndSmash: (
|
||||||
target: Option<Character>,
|
target: Option<Character>,
|
||||||
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
|
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = flow(
|
||||||
|
checkTargetIsSelected,
|
||||||
|
either.flatMap(trySmash),
|
||||||
|
);
|
||||||
|
|
||||||
export const checkTargetAndBurn: (
|
export const checkTargetAndBurn: (
|
||||||
target: Option<Character>,
|
target: Option<Character>,
|
||||||
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
|
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = flow(
|
||||||
|
checkTargetIsSelected,
|
||||||
|
either.flatMap(tryBurn),
|
||||||
|
);
|
||||||
|
|
||||||
export const checkTargetAndShoot: (
|
export const checkTargetAndShoot: (
|
||||||
target: Option<Character>,
|
target: Option<Character>,
|
||||||
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
|
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = flow(
|
||||||
|
checkTargetIsSelected,
|
||||||
|
either.flatMap(tryShoot),
|
||||||
|
);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// OPTION //
|
// OPTION //
|
||||||
|
@ -146,14 +198,20 @@ export const checkTargetAndShoot: (
|
||||||
// BONUS POINTS: If you properly defined small private helpers in the previous
|
// BONUS POINTS: If you properly defined small private helpers in the previous
|
||||||
// section, they should be easily reused for those use-cases.
|
// section, they should be easily reused for those use-cases.
|
||||||
|
|
||||||
export const smashOption: (character: Character) => Option<Damage> =
|
export const smashOption: (character: Character) => Option<Damage> = flow(
|
||||||
unimplemented;
|
trySmash,
|
||||||
|
option.fromEither,
|
||||||
|
);
|
||||||
|
|
||||||
export const burnOption: (character: Character) => Option<Damage> =
|
export const burnOption: (character: Character) => Option<Damage> = flow(
|
||||||
unimplemented;
|
tryBurn,
|
||||||
|
option.fromEither,
|
||||||
|
);
|
||||||
|
|
||||||
export const shootOption: (character: Character) => Option<Damage> =
|
export const shootOption: (character: Character) => Option<Damage> = flow(
|
||||||
unimplemented;
|
tryShoot,
|
||||||
|
option.fromEither,
|
||||||
|
);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// ARRAY //
|
// ARRAY //
|
||||||
|
@ -174,5 +232,24 @@ export interface TotalDamage {
|
||||||
[Damage.Ranged]: number;
|
[Damage.Ranged]: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const attack: (army: ReadonlyArray<Character>) => TotalDamage =
|
const emptyTotalDamage: () => TotalDamage = () => ({
|
||||||
unimplemented;
|
[Damage.Physical]: 0,
|
||||||
|
[Damage.Magical]: 0,
|
||||||
|
[Damage.Ranged]: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const attack: (army: ReadonlyArray<Character>) => TotalDamage = flow(
|
||||||
|
readonlyArray.filterMap((c) =>
|
||||||
|
isWarrior(c)
|
||||||
|
? smashOption(c)
|
||||||
|
: isWizard(c)
|
||||||
|
? burnOption(c)
|
||||||
|
: isArcher(c)
|
||||||
|
? shootOption(c)
|
||||||
|
: absurd(c) as Option<Damage>
|
||||||
|
),
|
||||||
|
readonlyArray.reduceRight(emptyTotalDamage(), (dmg, res) => {
|
||||||
|
res[dmg] += 1;
|
||||||
|
return res;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
// `fp-ts` training Exercise 3
|
// `fp-ts` training Exercise 3
|
||||||
// Sort things out with `Ord`
|
// Sort things out with `Ord`
|
||||||
|
|
||||||
import { Option } from 'fp-ts/Option';
|
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";
|
||||||
|
import { Ord } from "fp-ts/lib/Ord";
|
||||||
|
|
||||||
// Have you ever looked at the methods provided by `fp-ts` own `Array` and
|
// Have you ever looked at the methods provided by `fp-ts` own `Array` and
|
||||||
// `ReadonlyArray` modules? They expose a load of functions to manipulate
|
// `ReadonlyArray` modules? They expose a load of functions to manipulate
|
||||||
|
@ -36,11 +38,11 @@ import { unimplemented } from '../utils';
|
||||||
|
|
||||||
export const sortStrings: (
|
export const sortStrings: (
|
||||||
strings: ReadonlyArray<string>,
|
strings: ReadonlyArray<string>,
|
||||||
) => ReadonlyArray<string> = unimplemented;
|
) => ReadonlyArray<string> = readonlyArray.sort(string.Ord);
|
||||||
|
|
||||||
export const sortNumbers: (
|
export const sortNumbers: (
|
||||||
numbers: ReadonlyArray<number>,
|
numbers: ReadonlyArray<number>,
|
||||||
) => ReadonlyArray<number> = unimplemented;
|
) => ReadonlyArray<number> = readonlyArray.sort(number.Ord);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// REVERSE SORT //
|
// REVERSE SORT //
|
||||||
|
@ -57,7 +59,7 @@ export const sortNumbers: (
|
||||||
|
|
||||||
export const sortNumbersDescending: (
|
export const sortNumbersDescending: (
|
||||||
numbers: ReadonlyArray<number>,
|
numbers: ReadonlyArray<number>,
|
||||||
) => ReadonlyArray<number> = unimplemented;
|
) => ReadonlyArray<number> = readonlyArray.sort(ord.reverse(number.Ord));
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// SORT OPTIONAL VALUES //
|
// SORT OPTIONAL VALUES //
|
||||||
|
@ -75,7 +77,9 @@ export const sortNumbersDescending: (
|
||||||
|
|
||||||
export const sortOptionalNumbers: (
|
export const sortOptionalNumbers: (
|
||||||
optionalNumbers: ReadonlyArray<Option<number>>,
|
optionalNumbers: ReadonlyArray<Option<number>>,
|
||||||
) => ReadonlyArray<Option<number>> = unimplemented;
|
) => ReadonlyArray<Option<number>> = readonlyArray.sort(
|
||||||
|
option.getOrd(number.Ord),
|
||||||
|
);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// SORT COMPLEX OBJECTS //
|
// SORT COMPLEX OBJECTS //
|
||||||
|
@ -99,13 +103,25 @@ export interface Person {
|
||||||
readonly age: Option<number>;
|
readonly age: Option<number>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ordPersonName = string.Ord;
|
||||||
|
const ordPersonByName: Ord<Person> = pipe(
|
||||||
|
ordPersonName,
|
||||||
|
ord.contramap((p) => p.name),
|
||||||
|
);
|
||||||
|
|
||||||
|
const ordPersonAge = option.getOrd(number.Ord);
|
||||||
|
const ordPersonByAge: Ord<Person> = pipe(
|
||||||
|
ordPersonAge,
|
||||||
|
ord.contramap((p) => p.age),
|
||||||
|
);
|
||||||
|
|
||||||
export const sortPersonsByName: (
|
export const sortPersonsByName: (
|
||||||
persons: ReadonlyArray<Person>,
|
persons: ReadonlyArray<Person>,
|
||||||
) => ReadonlyArray<Person> = unimplemented;
|
) => ReadonlyArray<Person> = readonlyArray.sort(ordPersonByName);
|
||||||
|
|
||||||
export const sortPersonsByAge: (
|
export const sortPersonsByAge: (
|
||||||
persons: ReadonlyArray<Person>,
|
persons: ReadonlyArray<Person>,
|
||||||
) => ReadonlyArray<Person> = unimplemented;
|
) => ReadonlyArray<Person> = readonlyArray.sort(ordPersonByAge);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// COMBINE SORTING SCHEMES //
|
// COMBINE SORTING SCHEMES //
|
||||||
|
@ -118,4 +134,7 @@ export const sortPersonsByAge: (
|
||||||
|
|
||||||
export const sortPersonsByAgeThenByName: (
|
export const sortPersonsByAgeThenByName: (
|
||||||
persons: ReadonlyArray<Person>,
|
persons: ReadonlyArray<Person>,
|
||||||
) => ReadonlyArray<Person> = unimplemented;
|
) => ReadonlyArray<Person> = readonlyArray.sortBy([
|
||||||
|
ordPersonByAge,
|
||||||
|
ordPersonByName,
|
||||||
|
]);
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// `fp-ts` training Exercise 4
|
// `fp-ts` training Exercise 4
|
||||||
// Dependency injection with `Reader`
|
// Dependency injection with `Reader`
|
||||||
|
|
||||||
import { Reader } from 'fp-ts/Reader';
|
import { Reader } from "fp-ts/Reader";
|
||||||
|
|
||||||
import { unimplemented } from '../utils';
|
import { reader } from "fp-ts";
|
||||||
|
import { absurd, flow, pipe } from "fp-ts/lib/function";
|
||||||
|
|
||||||
// Sometimes, a function can have a huge amount of dependencies (services,
|
// Sometimes, a function can have a huge amount of dependencies (services,
|
||||||
// repositories, ...) and it is often impractical (not to say truly annoying)
|
// repositories, ...) and it is often impractical (not to say truly annoying)
|
||||||
|
@ -21,9 +22,9 @@ import { unimplemented } from '../utils';
|
||||||
// Let's consider a small range of countries (here, France, Spain and the USA):
|
// Let's consider a small range of countries (here, France, Spain and the USA):
|
||||||
|
|
||||||
export enum Country {
|
export enum Country {
|
||||||
France = 'France',
|
France = "France",
|
||||||
Spain = 'Spain',
|
Spain = "Spain",
|
||||||
USA = 'USA',
|
USA = "USA",
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -44,8 +45,21 @@ export enum Country {
|
||||||
//
|
//
|
||||||
// HINT: Take a look at `reader.ask` to access the environment value
|
// HINT: Take a look at `reader.ask` to access the environment value
|
||||||
|
|
||||||
export const exclamation: (sentence: string) => Reader<Country, string> =
|
export const exclamation: (sentence: string) => Reader<Country, string> = (
|
||||||
unimplemented();
|
sentence,
|
||||||
|
) =>
|
||||||
|
pipe(
|
||||||
|
reader.ask<Country>(),
|
||||||
|
reader.map((c) =>
|
||||||
|
c === Country.USA
|
||||||
|
? `${sentence}!`
|
||||||
|
: c === Country.France
|
||||||
|
? `${sentence} !`
|
||||||
|
: c === Country.Spain
|
||||||
|
? `¡${sentence}!`
|
||||||
|
: absurd(c)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Obviously, different countries often mean different languages and so
|
// Obviously, different countries often mean different languages and so
|
||||||
// different words for saying "Hello":
|
// different words for saying "Hello":
|
||||||
|
@ -53,11 +67,13 @@ export const exclamation: (sentence: string) => Reader<Country, string> =
|
||||||
export const sayHello = (country: Country): string => {
|
export const sayHello = (country: Country): string => {
|
||||||
switch (country) {
|
switch (country) {
|
||||||
case Country.France:
|
case Country.France:
|
||||||
return 'Bonjour';
|
return "Bonjour";
|
||||||
case Country.Spain:
|
case Country.Spain:
|
||||||
return 'Buenos dìas';
|
return "Buenos dìas";
|
||||||
case Country.USA:
|
case Country.USA:
|
||||||
return 'Hello';
|
return "Hello";
|
||||||
|
default:
|
||||||
|
return absurd(country);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -70,7 +86,8 @@ export const sayHello = (country: Country): string => {
|
||||||
// HINT: You can look into `reader.map` to modify the output of a `Reader`
|
// HINT: You can look into `reader.map` to modify the output of a `Reader`
|
||||||
// action.
|
// action.
|
||||||
|
|
||||||
export const greet: (name: string) => Reader<Country, string> = unimplemented();
|
export const greet: (name: string) => Reader<Country, string> = (name) =>
|
||||||
|
pipe(reader.ask<Country>(), reader.map((c) => `${sayHello(c)}, ${name}`));
|
||||||
|
|
||||||
// Finally, we are going to compose multiple `Reader`s together.
|
// Finally, we are going to compose multiple `Reader`s together.
|
||||||
//
|
//
|
||||||
|
@ -84,5 +101,7 @@ export const greet: (name: string) => Reader<Country, string> = unimplemented();
|
||||||
// HINT: As with other wrapper types in `fp-ts`, `reader` offers a way of
|
// HINT: As with other wrapper types in `fp-ts`, `reader` offers a way of
|
||||||
// composing effects with `reader.chain`.
|
// composing effects with `reader.chain`.
|
||||||
|
|
||||||
export const excitedlyGreet: (name: string) => Reader<Country, string> =
|
export const excitedlyGreet: (name: string) => Reader<Country, string> = flow(
|
||||||
unimplemented();
|
greet,
|
||||||
|
reader.flatMap(exclamation),
|
||||||
|
);
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
// `fp-ts` training Exercise 5
|
// `fp-ts` training Exercise 5
|
||||||
// Managing nested effectful data with `traverse`
|
// Managing nested effectful data with `traverse`
|
||||||
|
|
||||||
import { option, readonlyRecord, task } from 'fp-ts';
|
import { option, readonlyRecord, task, taskOption } from "fp-ts";
|
||||||
import { pipe } from 'fp-ts/lib/function';
|
import { flow, pipe } from "fp-ts/lib/function";
|
||||||
import { Option } from 'fp-ts/lib/Option';
|
import { Option } from "fp-ts/lib/Option";
|
||||||
import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord';
|
import { ReadonlyRecord } from "fp-ts/lib/ReadonlyRecord";
|
||||||
import { Task } from 'fp-ts/lib/Task';
|
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
|
// 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',
|
// get to a point when we have many nested types that we would like to 'merge',
|
||||||
|
@ -26,24 +26,23 @@ import { sleep, unimplemented, unimplementedAsync } from '../utils';
|
||||||
|
|
||||||
// Let's consider a small range of countries (here, France, Spain and the USA)
|
// Let's consider a small range of countries (here, France, Spain and the USA)
|
||||||
// with a mapping from their name to their code:
|
// with a mapping from their name to their code:
|
||||||
type CountryCode = 'FR' | 'SP' | 'US';
|
type CountryCode = "FR" | "SP" | "US";
|
||||||
export const countryNameToCountryCode: ReadonlyRecord<string, CountryCode> = {
|
export const countryNameToCountryCode: ReadonlyRecord<string, CountryCode> = {
|
||||||
France: 'FR',
|
France: "FR",
|
||||||
Spain: 'SP',
|
Spain: "SP",
|
||||||
USA: 'US',
|
USA: "US",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Let's simulate the call to an api which would return the currency when
|
// Let's simulate the call to an api which would return the currency when
|
||||||
// providing a country code. For the sake of simplicity, let's consider that it
|
// providing a country code. For the sake of simplicity, let's consider that it
|
||||||
// cannot fail.
|
// cannot fail.
|
||||||
type Currency = 'EUR' | 'DOLLAR';
|
type Currency = "EUR" | "DOLLAR";
|
||||||
export const getCountryCurrency: (countryCode: CountryCode) => Task<Currency> =
|
export const getCountryCurrency: (countryCode: CountryCode) => Task<Currency> =
|
||||||
(countryCode: CountryCode): Task<Currency> =>
|
(countryCode: CountryCode): Task<Currency> => async () => {
|
||||||
async () => {
|
if (countryCode === "US") {
|
||||||
if (countryCode === 'US') {
|
return "DOLLAR";
|
||||||
return 'DOLLAR';
|
|
||||||
}
|
}
|
||||||
return 'EUR';
|
return "EUR";
|
||||||
};
|
};
|
||||||
|
|
||||||
// Let's simulate a way for the user to provide a country name.
|
// Let's simulate a way for the user to provide a country name.
|
||||||
|
@ -68,9 +67,9 @@ export const getCountryCode: (countryName: string) => Option<CountryCode> = (
|
||||||
// country name and return its currency if it knows the country.
|
// country name and return its currency if it knows the country.
|
||||||
// A naive implementation would be mapping on each `Task` and `Option` to call
|
// A naive implementation would be mapping on each `Task` and `Option` to call
|
||||||
// the correct method:
|
// the correct method:
|
||||||
export const naiveGiveCurrencyOfCountryToUser = (
|
export const naiveGiveCurrencyOfCountryToUser: (
|
||||||
countryNameFromUserMock: string,
|
countryNameFromUserMock: string,
|
||||||
) =>
|
) => Task<Option<Task<Currency>>> = (countryNameFromUserMock) =>
|
||||||
pipe(
|
pipe(
|
||||||
getCountryNameFromUser(countryNameFromUserMock),
|
getCountryNameFromUser(countryNameFromUserMock),
|
||||||
task.map(getCountryCode),
|
task.map(getCountryCode),
|
||||||
|
@ -96,7 +95,9 @@ export const naiveGiveCurrencyOfCountryToUser = (
|
||||||
|
|
||||||
export const getCountryCurrencyOfOptionalCountryCode: (
|
export const getCountryCurrencyOfOptionalCountryCode: (
|
||||||
optionalCountryCode: Option<CountryCode>,
|
optionalCountryCode: Option<CountryCode>,
|
||||||
) => Task<Option<Currency>> = unimplementedAsync;
|
) => Task<Option<Currency>> = option.traverse(task.ApplicativePar)(
|
||||||
|
getCountryCurrency,
|
||||||
|
);
|
||||||
|
|
||||||
// Let's now use this function in our naive implementation's pipe to see how it
|
// Let's now use this function in our naive implementation's pipe to see how it
|
||||||
// improves it.
|
// improves it.
|
||||||
|
@ -108,12 +109,24 @@ export const getCountryCurrencyOfOptionalCountryCode: (
|
||||||
|
|
||||||
export const giveCurrencyOfCountryToUser: (
|
export const giveCurrencyOfCountryToUser: (
|
||||||
countryNameFromUserMock: string,
|
countryNameFromUserMock: string,
|
||||||
) => Task<Option<Currency>> = unimplementedAsync;
|
) => Task<Option<Currency>> = flow(
|
||||||
|
getCountryNameFromUser,
|
||||||
|
task.map(getCountryCode),
|
||||||
|
task.chain(getCountryCurrencyOfOptionalCountryCode),
|
||||||
|
);
|
||||||
|
|
||||||
// BONUS: We don't necessarily need `traverse` to do this. Try implementing
|
// BONUS: We don't necessarily need `traverse` to do this. Try implementing
|
||||||
// `giveCurrencyOfCountryToUser` by lifting some of the functions' results to
|
// `giveCurrencyOfCountryToUser` by lifting some of the functions' results to
|
||||||
// `TaskOption`
|
// `TaskOption`
|
||||||
|
|
||||||
|
export const giveCurrencyOfCountryToUser2: (
|
||||||
|
countryNameFromUserMock: string,
|
||||||
|
) => Task<Option<Currency>> = flow(
|
||||||
|
getCountryNameFromUser,
|
||||||
|
task.map(getCountryCode),
|
||||||
|
taskOption.flatMapTask(getCountryCurrency),
|
||||||
|
);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// TRAVERSING ARRAYS //
|
// TRAVERSING ARRAYS //
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -148,7 +161,7 @@ export const getCountryCodeOfCountryNames = (
|
||||||
|
|
||||||
export const getValidCountryCodeOfCountryNames: (
|
export const getValidCountryCodeOfCountryNames: (
|
||||||
countryNames: ReadonlyArray<string>,
|
countryNames: ReadonlyArray<string>,
|
||||||
) => Option<ReadonlyArray<CountryCode>> = unimplemented;
|
) => Option<ReadonlyArray<CountryCode>> = option.traverseArray(getCountryCode);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// TRAVERSING ARRAYS ASYNCHRONOUSLY //
|
// TRAVERSING ARRAYS ASYNCHRONOUSLY //
|
||||||
|
@ -165,7 +178,7 @@ export const getValidCountryCodeOfCountryNames: (
|
||||||
// Let's simulate a method that reads a number in a database, does some async
|
// Let's simulate a method that reads a number in a database, does some async
|
||||||
// computation with it, replaces this number in the database by the result of
|
// computation with it, replaces this number in the database by the result of
|
||||||
// the computation and returns it
|
// the computation and returns it
|
||||||
const createSimulatedAsyncMethod = (): ((toAdd: number) => Task<number>) => {
|
const createSimulatedAsyncMethod = (): (toAdd: number) => Task<number> => {
|
||||||
let number = 0;
|
let number = 0;
|
||||||
|
|
||||||
return (toAdd: number) => async () => {
|
return (toAdd: number) => async () => {
|
||||||
|
@ -186,7 +199,9 @@ const createSimulatedAsyncMethod = (): ((toAdd: number) => Task<number>) => {
|
||||||
export const simulatedAsyncMethodForParallel = createSimulatedAsyncMethod();
|
export const simulatedAsyncMethodForParallel = createSimulatedAsyncMethod();
|
||||||
export const performAsyncComputationInParallel: (
|
export const performAsyncComputationInParallel: (
|
||||||
numbers: ReadonlyArray<number>,
|
numbers: ReadonlyArray<number>,
|
||||||
) => Task<ReadonlyArray<number>> = unimplementedAsync;
|
) => Task<ReadonlyArray<number>> = task.traverseArray(
|
||||||
|
simulatedAsyncMethodForParallel,
|
||||||
|
);
|
||||||
|
|
||||||
// Write a method to traverse an array by running the method
|
// Write a method to traverse an array by running the method
|
||||||
// `simulatedAsyncMethodForSequence: (toAdd: number) => Task<number>`
|
// `simulatedAsyncMethodForSequence: (toAdd: number) => Task<number>`
|
||||||
|
@ -198,7 +213,9 @@ export const performAsyncComputationInParallel: (
|
||||||
export const simulatedAsyncMethodForSequence = createSimulatedAsyncMethod();
|
export const simulatedAsyncMethodForSequence = createSimulatedAsyncMethod();
|
||||||
export const performAsyncComputationInSequence: (
|
export const performAsyncComputationInSequence: (
|
||||||
numbers: ReadonlyArray<number>,
|
numbers: ReadonlyArray<number>,
|
||||||
) => Task<ReadonlyArray<number>> = unimplementedAsync;
|
) => Task<ReadonlyArray<number>> = task.traverseSeqArray(
|
||||||
|
simulatedAsyncMethodForSequence,
|
||||||
|
);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// SEQUENCE //
|
// SEQUENCE //
|
||||||
|
@ -220,11 +237,11 @@ export const performAsyncComputationInSequence: (
|
||||||
|
|
||||||
export const sequenceOptionTask: (
|
export const sequenceOptionTask: (
|
||||||
optionOfTask: Option<Task<Currency>>,
|
optionOfTask: Option<Task<Currency>>,
|
||||||
) => Task<Option<Currency>> = unimplementedAsync;
|
) => Task<Option<Currency>> = option.sequence(task.ApplicativePar);
|
||||||
|
|
||||||
export const sequenceOptionArray: (
|
export const sequenceOptionArray: (
|
||||||
arrayOfOptions: ReadonlyArray<Option<CountryCode>>,
|
arrayOfOptions: ReadonlyArray<Option<CountryCode>>,
|
||||||
) => Option<ReadonlyArray<CountryCode>> = unimplemented;
|
) => Option<ReadonlyArray<CountryCode>> = option.sequenceArray;
|
||||||
|
|
||||||
// BONUS: try using these two functions in the exercises 'TRAVERSING OPTIONS'
|
// BONUS: try using these two functions in the exercises 'TRAVERSING OPTIONS'
|
||||||
// and 'TRAVERSING ARRAYS' above
|
// and 'TRAVERSING ARRAYS' above
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
// `fp-ts` training Exercise 6
|
// `fp-ts` training Exercise 6
|
||||||
// Introduction to `ReaderTaskEither`
|
// Introduction to `ReaderTaskEither`
|
||||||
|
|
||||||
import { ReaderTaskEither } from 'fp-ts/lib/ReaderTaskEither';
|
import { ReaderTaskEither } from "fp-ts/lib/ReaderTaskEither";
|
||||||
import { unimplemented } from '../utils';
|
import { Application } from "./application";
|
||||||
import { Application } from './application';
|
import { User } from "./domain";
|
||||||
import { User } from './domain';
|
import { flow, pipe } from "fp-ts/lib/function";
|
||||||
|
import { readerTaskEither as rte } from "fp-ts";
|
||||||
|
|
||||||
// In real world applications you will mostly manipulate `ReaderTaskEither` aka
|
// In real world applications you will mostly manipulate `ReaderTaskEither` aka
|
||||||
// `rte` in the use-cases of the application.
|
// `rte` in the use-cases of the application.
|
||||||
|
@ -23,13 +24,20 @@ import { User } from './domain';
|
||||||
// current context. In the following example, we need to fetch a user by its id
|
// current context. In the following example, we need to fetch a user by its id
|
||||||
// and then we want to return its capitalized.
|
// and then we want to return its capitalized.
|
||||||
|
|
||||||
|
const capitalizeUserName: (user: User.User) => string = (user) =>
|
||||||
|
user.name[0].toUpperCase() + user.name.slice(1);
|
||||||
|
|
||||||
export const getCapitalizedUserName: (args: {
|
export const getCapitalizedUserName: (args: {
|
||||||
userId: string;
|
userId: string;
|
||||||
}) => ReaderTaskEither<
|
}) => ReaderTaskEither<
|
||||||
User.Repository.Access,
|
User.Repository.Access,
|
||||||
User.Repository.UserNotFoundError,
|
User.Repository.UserNotFoundError,
|
||||||
string
|
string
|
||||||
> = unimplemented;
|
> = ({ userId }) =>
|
||||||
|
pipe(
|
||||||
|
User.Repository.getById(userId),
|
||||||
|
rte.map(capitalizeUserName),
|
||||||
|
);
|
||||||
|
|
||||||
// Sometimes you will need to get multiple data points before performing an operation
|
// 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.
|
// on them. In this case, it is very convenient to use the `Do` notation.
|
||||||
|
@ -52,7 +60,13 @@ export const getConcatenationOfTheTwoUserNames: (args: {
|
||||||
User.Repository.Access,
|
User.Repository.Access,
|
||||||
User.Repository.UserNotFoundError,
|
User.Repository.UserNotFoundError,
|
||||||
string
|
string
|
||||||
> = unimplemented;
|
> = ({ userIdOne, userIdTwo }) =>
|
||||||
|
pipe(
|
||||||
|
rte.Do,
|
||||||
|
rte.apS("userOneName", getCapitalizedUserName({ userId: userIdOne })),
|
||||||
|
rte.apS("userTwoName", getCapitalizedUserName({ userId: userIdTwo })),
|
||||||
|
rte.map(({ userOneName, userTwoName }) => userOneName + userTwoName),
|
||||||
|
);
|
||||||
|
|
||||||
// Sometimes, you will need to feed the current context with data that you can
|
// Sometimes, you will need to feed the current context with data that you can
|
||||||
// only retrieve after performing some operations, in other words, operations
|
// only retrieve after performing some operations, in other words, operations
|
||||||
|
@ -69,7 +83,16 @@ export const getConcatenationOfTheBestFriendNameAndUserName: (args: {
|
||||||
User.Repository.Access,
|
User.Repository.Access,
|
||||||
User.Repository.UserNotFoundError,
|
User.Repository.UserNotFoundError,
|
||||||
string
|
string
|
||||||
> = unimplemented;
|
> = ({ userIdOne }) =>
|
||||||
|
pipe(
|
||||||
|
rte.Do,
|
||||||
|
rte.apS("user", User.Repository.getById(userIdOne)),
|
||||||
|
rte.bindW("bestFriend", ({ user }) =>
|
||||||
|
User.Repository.getById(user.bestFriendId)),
|
||||||
|
rte.map(({ user, bestFriend }) =>
|
||||||
|
capitalizeUserName(user) + capitalizeUserName(bestFriend)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Most of the time, you will need to use several external services.
|
// Most of the time, you will need to use several external services.
|
||||||
// The challenge of this usecase is to use TimeService in the flow of our `rte`
|
// The challenge of this usecase is to use TimeService in the flow of our `rte`
|
||||||
|
@ -81,4 +104,13 @@ export const getConcatenationOfUserNameAndCurrentYear: (args: {
|
||||||
Dependencies,
|
Dependencies,
|
||||||
User.Repository.UserNotFoundError,
|
User.Repository.UserNotFoundError,
|
||||||
string
|
string
|
||||||
> = unimplemented;
|
> = ({ userIdOne }) =>
|
||||||
|
pipe(
|
||||||
|
rte.Do,
|
||||||
|
rte.apS("user", User.Repository.getById(userIdOne)),
|
||||||
|
rte.bindW(
|
||||||
|
"currentYear",
|
||||||
|
flow(Application.TimeService.thisYear, rte.fromReader),
|
||||||
|
),
|
||||||
|
rte.map(({ user, currentYear }) => user.name + currentYear),
|
||||||
|
);
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
// `fp-ts` training Exercise 7
|
// `fp-ts` training Exercise 7
|
||||||
// Manipulate collections with type-classes
|
// 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
|
// In this exercise, we will learn how to manipulate essential collections
|
||||||
// such as `Set` and `Map`.
|
// such as `Set` and `Map`.
|
||||||
|
@ -41,7 +49,9 @@ export const numberArray: ReadonlyArray<number> = [7, 42, 1337, 1, 0, 1337, 42];
|
||||||
// - `fp-ts` doesn't know how you want to define equality for the inner type
|
// - `fp-ts` doesn't know how you want to define equality for the inner type
|
||||||
// and requires you to provide an `Eq` instance
|
// and requires you to provide an `Eq` instance
|
||||||
|
|
||||||
export const numberSet: ReadonlySet<number> = unimplemented();
|
export const numberSet: ReadonlySet<number> = readonlySet.fromReadonlyArray(
|
||||||
|
number.Eq,
|
||||||
|
)(numberArray);
|
||||||
|
|
||||||
// Convert `numberSet` back to an array in `numberArrayFromSet`.
|
// Convert `numberSet` back to an array in `numberArrayFromSet`.
|
||||||
// You need to use the `ReadonlySet` module from `fp-ts` instead of the
|
// You need to use the `ReadonlySet` module from `fp-ts` instead of the
|
||||||
|
@ -57,7 +67,8 @@ export const numberSet: ReadonlySet<number> = unimplemented();
|
||||||
// the values to be ordered in the output array, by providing an `Ord`
|
// the values to be ordered in the output array, by providing an `Ord`
|
||||||
// instance.
|
// instance.
|
||||||
|
|
||||||
export const numberArrayFromSet: ReadonlyArray<number> = unimplemented();
|
export const numberArrayFromSet: ReadonlyArray<number> = readonlySet
|
||||||
|
.toReadonlyArray(number.Ord)(numberSet);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// MAP //
|
// MAP //
|
||||||
|
@ -73,11 +84,11 @@ export const numberArrayFromSet: ReadonlyArray<number> = unimplemented();
|
||||||
// numbers, etc...)
|
// numbers, etc...)
|
||||||
|
|
||||||
export const associativeArray: ReadonlyArray<[number, string]> = [
|
export const associativeArray: ReadonlyArray<[number, string]> = [
|
||||||
[1, 'Alice'],
|
[1, "Alice"],
|
||||||
[2, 'Bob'],
|
[2, "Bob"],
|
||||||
[3, 'Clara'],
|
[3, "Clara"],
|
||||||
[4, 'Denise'],
|
[4, "Denise"],
|
||||||
[2, 'Robert'],
|
[2, "Robert"],
|
||||||
];
|
];
|
||||||
|
|
||||||
// Construct `mapWithLastEntry` from the provided `associativeArray`.
|
// Construct `mapWithLastEntry` from the provided `associativeArray`.
|
||||||
|
@ -101,7 +112,14 @@ export const associativeArray: ReadonlyArray<[number, string]> = [
|
||||||
// long as they implement `Foldable`. Here, you can simply pass the standard
|
// long as they implement `Foldable`. Here, you can simply pass the standard
|
||||||
// `readonlyArray.Foldable` instance.
|
// `readonlyArray.Foldable` instance.
|
||||||
|
|
||||||
export const mapWithLastEntry: ReadonlyMap<number, string> = unimplemented();
|
export const mapWithLastEntry: ReadonlyMap<number, string> = pipe(
|
||||||
|
associativeArray,
|
||||||
|
readonlyMap.fromFoldable(
|
||||||
|
number.Eq,
|
||||||
|
semigroup.last<string>(),
|
||||||
|
readonlyArray.Foldable,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Same thing as above, except that upon key collision we don't want to simply
|
// 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.
|
// select the newest entry value but append it to the previous one.
|
||||||
|
@ -121,8 +139,14 @@ export const mapWithLastEntry: ReadonlyMap<number, string> = unimplemented();
|
||||||
// Did you find something in the `Semigroup` module that may have been
|
// Did you find something in the `Semigroup` module that may have been
|
||||||
// helpful in defining `mapWithLastEntry`?
|
// helpful in defining `mapWithLastEntry`?
|
||||||
|
|
||||||
export const mapWithConcatenatedEntries: ReadonlyMap<number, string> =
|
export const mapWithConcatenatedEntries: ReadonlyMap<number, string> = pipe(
|
||||||
unimplemented();
|
associativeArray,
|
||||||
|
readonlyMap.fromFoldable(
|
||||||
|
number.Eq,
|
||||||
|
string.Semigroup,
|
||||||
|
readonlyArray.Foldable,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// DIFFERENCE / UNION / INTERSECTION //
|
// DIFFERENCE / UNION / INTERSECTION //
|
||||||
|
@ -137,12 +161,18 @@ export const odds = new Set([1, 3, 5, 7, 9]);
|
||||||
// HINT:
|
// HINT:
|
||||||
// - Be mindful of the order of operands for the operator you will choose.
|
// - Be mindful of the order of operands for the operator you will choose.
|
||||||
|
|
||||||
export const nonPrimeOdds: ReadonlySet<number> = unimplemented();
|
export const nonPrimeOdds: ReadonlySet<number> = pipe(
|
||||||
|
odds,
|
||||||
|
readonlySet.difference(number.Eq)(primes),
|
||||||
|
);
|
||||||
|
|
||||||
// Construct the set `primeOdds` from the two sets defined above. It should
|
// Construct the set `primeOdds` from the two sets defined above. It should
|
||||||
// only include the odd numbers that are also prime.
|
// only include the odd numbers that are also prime.
|
||||||
|
|
||||||
export const primeOdds: ReadonlySet<number> = unimplemented();
|
export const primeOdds: ReadonlySet<number> = pipe(
|
||||||
|
odds,
|
||||||
|
readonlySet.intersection(number.Eq)(primes),
|
||||||
|
);
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -159,29 +189,43 @@ export type Analytics = {
|
||||||
|
|
||||||
export const pageViewsA = new Map(
|
export const pageViewsA = new Map(
|
||||||
[
|
[
|
||||||
{ page: 'home', views: 5 },
|
{ page: "home", views: 5 },
|
||||||
{ page: 'about', views: 2 },
|
{ page: "about", views: 2 },
|
||||||
{ page: 'blog', views: 7 },
|
{ page: "blog", views: 7 },
|
||||||
].map(entry => [entry.page, entry]),
|
].map((entry) => [entry.page, entry]),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const pageViewsB = new Map(
|
export const pageViewsB = new Map(
|
||||||
[
|
[
|
||||||
{ page: 'home', views: 10 },
|
{ page: "home", views: 10 },
|
||||||
{ page: 'blog', views: 35 },
|
{ page: "blog", views: 35 },
|
||||||
{ page: 'faq', views: 5 },
|
{ page: "faq", views: 5 },
|
||||||
].map(entry => [entry.page, entry]),
|
].map((entry) => [entry.page, entry]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const AnalyticsSemigroup = semigroup.struct({
|
||||||
|
page: semigroup.first<string>(),
|
||||||
|
views: number.SemigroupSum,
|
||||||
|
});
|
||||||
|
|
||||||
// Construct the `Map` with the total page views for all the pages in both sources
|
// Construct the `Map` with the total page views for all the pages in both sources
|
||||||
// of analytics `pageViewsA` and `pageViewsB`.
|
// of analytics `pageViewsA` and `pageViewsB`.
|
||||||
//
|
//
|
||||||
// In case a page appears in both sources, their view count should be summed.
|
// In case a page appears in both sources, their view count should be summed.
|
||||||
|
|
||||||
export const allPageViews: ReadonlyMap<string, Analytics> = unimplemented();
|
export const allPageViews: ReadonlyMap<string, Analytics> = pipe(
|
||||||
|
pageViewsA,
|
||||||
|
readonlyMap.union(string.Eq, AnalyticsSemigroup)(
|
||||||
|
pageViewsB,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Construct the `Map` with the total page views but only for the pages that
|
// Construct the `Map` with the total page views but only for the pages that
|
||||||
// appear in both sources of analytics `pageViewsA` and `pageViewsB`.
|
// appear in both sources of analytics `pageViewsA` and `pageViewsB`.
|
||||||
|
|
||||||
export const intersectionPageViews: ReadonlyMap<string, Analytics> =
|
export const intersectionPageViews: ReadonlyMap<string, Analytics> = pipe(
|
||||||
unimplemented();
|
pageViewsA,
|
||||||
|
readonlyMap.intersection(string.Eq, AnalyticsSemigroup)(
|
||||||
|
pageViewsB,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
// `fp-ts` training Exercise 8
|
// `fp-ts` training Exercise 8
|
||||||
// Define your own combinators
|
// Define your own combinators
|
||||||
|
|
||||||
import { Either } from 'fp-ts/Either';
|
import { Either } from "fp-ts/Either";
|
||||||
import { ReaderTaskEither } from 'fp-ts/ReaderTaskEither';
|
import { ReaderTaskEither } from "fp-ts/ReaderTaskEither";
|
||||||
import { unimplemented } from '../utils';
|
import { readerTaskEither as rte } from "fp-ts";
|
||||||
|
import { Reader } from "fp-ts/lib/Reader";
|
||||||
|
|
||||||
// Technically, a combinator is a pure function with no free variables in it,
|
// Technically, a combinator is a pure function with no free variables in it,
|
||||||
// ie. one that does not depend on any variable from its enclosing scope.
|
// ie. one that does not depend on any variable from its enclosing scope.
|
||||||
|
@ -75,12 +76,21 @@ export const bindEitherK: <N extends string, A, E, B>(
|
||||||
R,
|
R,
|
||||||
E,
|
E,
|
||||||
{ readonly [K in N | keyof A]: K extends keyof A ? A[K] : B }
|
{ readonly [K in N | keyof A]: K extends keyof A ? A[K] : B }
|
||||||
> = unimplemented;
|
> = (name, f) => rte.bind(name, (a) => rte.fromEither(f(a)));
|
||||||
|
|
||||||
// Write the implementation and type definition of `bindEitherKW`, the
|
// Write the implementation and type definition of `bindEitherKW`, the
|
||||||
// "Widened" version of `bindEitherK`.
|
// "Widened" version of `bindEitherK`.
|
||||||
|
|
||||||
export const bindEitherKW = unimplemented;
|
export const bindEitherKW: <N extends string, A, E2, B>(
|
||||||
|
name: Exclude<N, keyof A>,
|
||||||
|
f: (a: A) => Either<E2, B>,
|
||||||
|
) => <R, E1>(
|
||||||
|
ma: ReaderTaskEither<R, E1, A>,
|
||||||
|
) => 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
|
// Write the implementations and type definitions of `apSEitherK` and
|
||||||
// `apSEitherKW`.
|
// `apSEitherKW`.
|
||||||
|
@ -89,9 +99,27 @@ export const bindEitherKW = unimplemented;
|
||||||
// - remember that "widen" in the case of `Either` means the union of the
|
// - remember that "widen" in the case of `Either` means the union of the
|
||||||
// possible error types
|
// possible error types
|
||||||
|
|
||||||
export const apSEitherK = unimplemented;
|
export const apSEitherK: <N extends string, A, E, B>(
|
||||||
|
name: Exclude<N, keyof A>,
|
||||||
|
fa: Either<E, B>,
|
||||||
|
) => <R>(
|
||||||
|
ma: ReaderTaskEither<R, E, A>,
|
||||||
|
) => ReaderTaskEither<
|
||||||
|
R,
|
||||||
|
E,
|
||||||
|
{ readonly [K in N | keyof A]: K extends keyof A ? A[K] : B }
|
||||||
|
> = (name, fa) => rte.apS(name, rte.fromEither(fa));
|
||||||
|
|
||||||
export const apSEitherKW = unimplemented;
|
export const apSEitherKW: <N extends string, A, E2, B>(
|
||||||
|
name: Exclude<N, keyof A>,
|
||||||
|
fa: Either<E2, B>,
|
||||||
|
) => <R, E1>(
|
||||||
|
ma: ReaderTaskEither<R, E1, A>,
|
||||||
|
) => ReaderTaskEither<
|
||||||
|
R,
|
||||||
|
E2 | E1,
|
||||||
|
{ readonly [K in N | keyof A]: K extends keyof A ? A[K] : B }
|
||||||
|
> = (name, fa) => rte.apSW(name, rte.fromEither(fa));
|
||||||
|
|
||||||
// Write the implementations and type definitions of `bindReaderK` and
|
// Write the implementations and type definitions of `bindReaderK` and
|
||||||
// `bindReaderKW`.
|
// `bindReaderKW`.
|
||||||
|
@ -100,6 +128,24 @@ export const apSEitherKW = unimplemented;
|
||||||
// - remember that "widen" in the case of `Reader` means the intersection of
|
// - remember that "widen" in the case of `Reader` means the intersection of
|
||||||
// the possible environment types
|
// the possible environment types
|
||||||
|
|
||||||
export const bindReaderK = unimplemented;
|
export const bindReaderK: <N extends string, A, R, B>(
|
||||||
|
name: Exclude<N, keyof A>,
|
||||||
|
f: (a: A) => Reader<R, B>,
|
||||||
|
) => <E>(
|
||||||
|
ma: ReaderTaskEither<R, E, A>,
|
||||||
|
) => ReaderTaskEither<
|
||||||
|
R,
|
||||||
|
E,
|
||||||
|
{ readonly [K in N | keyof A]: K extends keyof A ? A[K] : B }
|
||||||
|
> = (name, f) => rte.bind(name, (a) => rte.fromReader(f(a)));
|
||||||
|
|
||||||
export const bindReaderKW = unimplemented;
|
export const bindReaderKW: <N extends string, A, R2, B>(
|
||||||
|
name: Exclude<N, keyof A>,
|
||||||
|
f: (a: A) => Reader<R2, B>,
|
||||||
|
) => <E, R1>(
|
||||||
|
ma: ReaderTaskEither<R1, E, A>,
|
||||||
|
) => ReaderTaskEither<
|
||||||
|
R1 & R2,
|
||||||
|
E,
|
||||||
|
{ readonly [K in N | keyof A]: K extends keyof A ? A[K] : B }
|
||||||
|
> = (name, f) => rte.bindW(name, (a) => rte.fromReader(f(a)));
|
||||||
|
|
Reference in a new issue