do all tasks
This commit is contained in:
parent
f867fef92d
commit
250e2397f1
14 changed files with 7651 additions and 4833 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -13,4 +13,7 @@ node_modules
|
|||
|
||||
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`
|
||||
// - `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;
|
||||
|
||||
|
@ -42,9 +42,10 @@ 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) => 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
|
||||
// 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.
|
||||
|
||||
export const ifThenElse =
|
||||
<A>(onTrue: () => A, onFalse: () => A) =>
|
||||
(condition: boolean) =>
|
||||
<A>(onTrue: () => A, onFalse: () => A) => (condition: boolean) =>
|
||||
condition ? onTrue() : onFalse();
|
||||
|
||||
// Using `pipe` and `ifThenElse`, write the function that computes the next step in the Collatz
|
||||
// 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
|
||||
// a_n from the Collatz sequence, returns the number a_n+3 (ie. 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);
|
||||
|
|
|
@ -4,10 +4,12 @@
|
|||
// - 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 { Either } from "fp-ts/Either";
|
||||
import { Option } from "fp-ts/Option";
|
||||
import { TaskEither } from "fp-ts/TaskEither";
|
||||
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 => {
|
||||
return a / b;
|
||||
|
@ -24,8 +26,12 @@ export const divide = (a: number, b: number): number => {
|
|||
// - `option.some(value)`
|
||||
// - `option.none`
|
||||
|
||||
export const safeDivide: (a: number, b: number) => Option<number> =
|
||||
unimplemented;
|
||||
export const safeDivide: (a: number, b: number) => Option<number> = (a, b) =>
|
||||
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!
|
||||
// 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)`
|
||||
|
||||
// Here is an simple error type to help you:
|
||||
export type DivisionByZeroError = 'Error: Division by zero';
|
||||
export const DivisionByZero = 'Error: Division by zero' as const;
|
||||
export type DivisionByZeroError = "Error: Division by zero";
|
||||
export const DivisionByZero = "Error: Division by zero" as const;
|
||||
|
||||
export const safeDivideWithError: (
|
||||
a: number,
|
||||
b: number,
|
||||
) => Either<DivisionByZeroError, number> = unimplemented;
|
||||
) => Either<DivisionByZeroError, number> = flow(
|
||||
safeDivide,
|
||||
either.fromOption(() => DivisionByZero),
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// TASKEITHER //
|
||||
|
@ -70,7 +79,7 @@ export const asyncDivide = async (a: number, b: number) => {
|
|||
await sleep(1000);
|
||||
|
||||
if (b === 0) {
|
||||
throw new Error('BOOM!');
|
||||
throw new Error("BOOM!");
|
||||
}
|
||||
|
||||
return a / b;
|
||||
|
@ -86,4 +95,5 @@ export const asyncDivide = async (a: number, b: number) => {
|
|||
export const asyncSafeDivideWithError: (
|
||||
a: 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
|
||||
// Let's have fun with combinators!
|
||||
|
||||
import { Either } from 'fp-ts/Either';
|
||||
import { Option } from 'fp-ts/Option';
|
||||
import { Failure } from '../Failure';
|
||||
import { unimplemented } from '../utils';
|
||||
import { Either } from "fp-ts/Either";
|
||||
import { Option } from "fp-ts/Option";
|
||||
import { Failure } from "../Failure";
|
||||
import { either, option, readonlyArray } from "fp-ts";
|
||||
import { absurd, flow } from "fp-ts/lib/function";
|
||||
import { Refinement } from "fp-ts/lib/Refinement";
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SETUP //
|
||||
|
@ -20,9 +22,9 @@ export type Character = Warrior | Wizard | Archer;
|
|||
|
||||
// We have three types of `Damage`, each corresponding to a character type.
|
||||
export enum Damage {
|
||||
Physical = 'Physical damage',
|
||||
Magical = 'Magical damage',
|
||||
Ranged = 'Ranged damage',
|
||||
Physical = "Physical damage",
|
||||
Magical = "Magical damage",
|
||||
Ranged = "Ranged damage",
|
||||
}
|
||||
|
||||
// A `Warrior` only can output physical damage.
|
||||
|
@ -32,7 +34,7 @@ export class Warrior {
|
|||
}
|
||||
|
||||
toString() {
|
||||
return 'Warrior';
|
||||
return "Warrior";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,7 +45,7 @@ export class Wizard {
|
|||
}
|
||||
|
||||
toString() {
|
||||
return 'Wizard';
|
||||
return "Wizard";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,7 +56,7 @@ export class Archer {
|
|||
}
|
||||
|
||||
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
|
||||
|
||||
export enum Exo2FailureType {
|
||||
NoTarget = 'Exo2FailureType_NoTarget',
|
||||
InvalidTarget = 'Exo2FailureType_InvalidTarget',
|
||||
NoTarget = "Exo2FailureType_NoTarget",
|
||||
InvalidTarget = "Exo2FailureType_InvalidTarget",
|
||||
}
|
||||
|
||||
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
|
||||
// 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: (
|
||||
target: Option<Character>,
|
||||
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
|
||||
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = flow(
|
||||
checkTargetIsSelected,
|
||||
either.flatMap(trySmash),
|
||||
);
|
||||
|
||||
export const checkTargetAndBurn: (
|
||||
target: Option<Character>,
|
||||
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
|
||||
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = flow(
|
||||
checkTargetIsSelected,
|
||||
either.flatMap(tryBurn),
|
||||
);
|
||||
|
||||
export const checkTargetAndShoot: (
|
||||
target: Option<Character>,
|
||||
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = unimplemented;
|
||||
) => Either<NoTargetFailure | InvalidTargetFailure, Damage> = flow(
|
||||
checkTargetIsSelected,
|
||||
either.flatMap(tryShoot),
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// OPTION //
|
||||
|
@ -146,14 +198,20 @@ 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 smashOption: (character: Character) => Option<Damage> = flow(
|
||||
trySmash,
|
||||
option.fromEither,
|
||||
);
|
||||
|
||||
export const burnOption: (character: Character) => Option<Damage> =
|
||||
unimplemented;
|
||||
export const burnOption: (character: Character) => Option<Damage> = flow(
|
||||
tryBurn,
|
||||
option.fromEither,
|
||||
);
|
||||
|
||||
export const shootOption: (character: Character) => Option<Damage> =
|
||||
unimplemented;
|
||||
export const shootOption: (character: Character) => Option<Damage> = flow(
|
||||
tryShoot,
|
||||
option.fromEither,
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// ARRAY //
|
||||
|
@ -174,5 +232,24 @@ export interface TotalDamage {
|
|||
[Damage.Ranged]: number;
|
||||
}
|
||||
|
||||
export const attack: (army: ReadonlyArray<Character>) => TotalDamage =
|
||||
unimplemented;
|
||||
const emptyTotalDamage: () => TotalDamage = () => ({
|
||||
[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
|
||||
// Sort things out with `Ord`
|
||||
|
||||
import { Option } from 'fp-ts/Option';
|
||||
import { unimplemented } from '../utils';
|
||||
import { Option } from "fp-ts/Option";
|
||||
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
|
||||
// `ReadonlyArray` modules? They expose a load of functions to manipulate
|
||||
|
@ -36,11 +38,11 @@ import { unimplemented } from '../utils';
|
|||
|
||||
export const sortStrings: (
|
||||
strings: ReadonlyArray<string>,
|
||||
) => ReadonlyArray<string> = unimplemented;
|
||||
) => ReadonlyArray<string> = readonlyArray.sort(string.Ord);
|
||||
|
||||
export const sortNumbers: (
|
||||
numbers: ReadonlyArray<number>,
|
||||
) => ReadonlyArray<number> = unimplemented;
|
||||
) => ReadonlyArray<number> = readonlyArray.sort(number.Ord);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// REVERSE SORT //
|
||||
|
@ -57,7 +59,7 @@ export const sortNumbers: (
|
|||
|
||||
export const sortNumbersDescending: (
|
||||
numbers: ReadonlyArray<number>,
|
||||
) => ReadonlyArray<number> = unimplemented;
|
||||
) => ReadonlyArray<number> = readonlyArray.sort(ord.reverse(number.Ord));
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SORT OPTIONAL VALUES //
|
||||
|
@ -75,7 +77,9 @@ export const sortNumbersDescending: (
|
|||
|
||||
export const sortOptionalNumbers: (
|
||||
optionalNumbers: ReadonlyArray<Option<number>>,
|
||||
) => ReadonlyArray<Option<number>> = unimplemented;
|
||||
) => ReadonlyArray<Option<number>> = readonlyArray.sort(
|
||||
option.getOrd(number.Ord),
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SORT COMPLEX OBJECTS //
|
||||
|
@ -99,13 +103,25 @@ export interface Person {
|
|||
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: (
|
||||
persons: ReadonlyArray<Person>,
|
||||
) => ReadonlyArray<Person> = unimplemented;
|
||||
) => ReadonlyArray<Person> = readonlyArray.sort(ordPersonByName);
|
||||
|
||||
export const sortPersonsByAge: (
|
||||
persons: ReadonlyArray<Person>,
|
||||
) => ReadonlyArray<Person> = unimplemented;
|
||||
) => ReadonlyArray<Person> = readonlyArray.sort(ordPersonByAge);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// COMBINE SORTING SCHEMES //
|
||||
|
@ -118,4 +134,7 @@ export const sortPersonsByAge: (
|
|||
|
||||
export const sortPersonsByAgeThenByName: (
|
||||
persons: ReadonlyArray<Person>,
|
||||
) => ReadonlyArray<Person> = unimplemented;
|
||||
) => ReadonlyArray<Person> = readonlyArray.sortBy([
|
||||
ordPersonByAge,
|
||||
ordPersonByName,
|
||||
]);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
// `fp-ts` training Exercise 4
|
||||
// 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,
|
||||
// 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):
|
||||
|
||||
export enum Country {
|
||||
France = 'France',
|
||||
Spain = 'Spain',
|
||||
USA = 'USA',
|
||||
France = "France",
|
||||
Spain = "Spain",
|
||||
USA = "USA",
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -44,8 +45,21 @@ export enum Country {
|
|||
//
|
||||
// HINT: Take a look at `reader.ask` to access the environment value
|
||||
|
||||
export const exclamation: (sentence: string) => Reader<Country, string> =
|
||||
unimplemented();
|
||||
export const exclamation: (sentence: string) => Reader<Country, string> = (
|
||||
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
|
||||
// different words for saying "Hello":
|
||||
|
@ -53,11 +67,13 @@ export const exclamation: (sentence: string) => Reader<Country, string> =
|
|||
export const sayHello = (country: Country): string => {
|
||||
switch (country) {
|
||||
case Country.France:
|
||||
return 'Bonjour';
|
||||
return "Bonjour";
|
||||
case Country.Spain:
|
||||
return 'Buenos dìas';
|
||||
return "Buenos dìas";
|
||||
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`
|
||||
// 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.
|
||||
//
|
||||
|
@ -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
|
||||
// composing effects with `reader.chain`.
|
||||
|
||||
export const excitedlyGreet: (name: string) => Reader<Country, string> =
|
||||
unimplemented();
|
||||
export const excitedlyGreet: (name: string) => Reader<Country, string> = flow(
|
||||
greet,
|
||||
reader.flatMap(exclamation),
|
||||
);
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
// `fp-ts` training Exercise 5
|
||||
// Managing nested effectful data with `traverse`
|
||||
|
||||
import { option, readonlyRecord, task } from 'fp-ts';
|
||||
import { pipe } from 'fp-ts/lib/function';
|
||||
import { Option } from 'fp-ts/lib/Option';
|
||||
import { ReadonlyRecord } from 'fp-ts/lib/ReadonlyRecord';
|
||||
import { Task } from 'fp-ts/lib/Task';
|
||||
import { sleep, unimplemented, unimplementedAsync } from '../utils';
|
||||
import { option, readonlyRecord, task, taskOption } from "fp-ts";
|
||||
import { flow, pipe } from "fp-ts/lib/function";
|
||||
import { Option } from "fp-ts/lib/Option";
|
||||
import { ReadonlyRecord } from "fp-ts/lib/ReadonlyRecord";
|
||||
import { Task } from "fp-ts/lib/Task";
|
||||
import { sleep } from "../utils";
|
||||
|
||||
// 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',
|
||||
|
@ -26,24 +26,23 @@ import { sleep, unimplemented, unimplementedAsync } from '../utils';
|
|||
|
||||
// Let's consider a small range of countries (here, France, Spain and the USA)
|
||||
// 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> = {
|
||||
France: 'FR',
|
||||
Spain: 'SP',
|
||||
USA: 'US',
|
||||
France: "FR",
|
||||
Spain: "SP",
|
||||
USA: "US",
|
||||
};
|
||||
|
||||
// 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
|
||||
// cannot fail.
|
||||
type Currency = 'EUR' | 'DOLLAR';
|
||||
type Currency = "EUR" | "DOLLAR";
|
||||
export const getCountryCurrency: (countryCode: CountryCode) => Task<Currency> =
|
||||
(countryCode: CountryCode): Task<Currency> =>
|
||||
async () => {
|
||||
if (countryCode === 'US') {
|
||||
return 'DOLLAR';
|
||||
(countryCode: CountryCode): Task<Currency> => async () => {
|
||||
if (countryCode === "US") {
|
||||
return "DOLLAR";
|
||||
}
|
||||
return 'EUR';
|
||||
return "EUR";
|
||||
};
|
||||
|
||||
// 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.
|
||||
// A naive implementation would be mapping on each `Task` and `Option` to call
|
||||
// the correct method:
|
||||
export const naiveGiveCurrencyOfCountryToUser = (
|
||||
export const naiveGiveCurrencyOfCountryToUser: (
|
||||
countryNameFromUserMock: string,
|
||||
) =>
|
||||
) => Task<Option<Task<Currency>>> = (countryNameFromUserMock) =>
|
||||
pipe(
|
||||
getCountryNameFromUser(countryNameFromUserMock),
|
||||
task.map(getCountryCode),
|
||||
|
@ -96,7 +95,9 @@ export const naiveGiveCurrencyOfCountryToUser = (
|
|||
|
||||
export const getCountryCurrencyOfOptionalCountryCode: (
|
||||
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
|
||||
// improves it.
|
||||
|
@ -108,12 +109,24 @@ export const getCountryCurrencyOfOptionalCountryCode: (
|
|||
|
||||
export const giveCurrencyOfCountryToUser: (
|
||||
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
|
||||
// `giveCurrencyOfCountryToUser` by lifting some of the functions' results to
|
||||
// `TaskOption`
|
||||
|
||||
export const giveCurrencyOfCountryToUser2: (
|
||||
countryNameFromUserMock: string,
|
||||
) => Task<Option<Currency>> = flow(
|
||||
getCountryNameFromUser,
|
||||
task.map(getCountryCode),
|
||||
taskOption.flatMapTask(getCountryCurrency),
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// TRAVERSING ARRAYS //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -148,7 +161,7 @@ export const getCountryCodeOfCountryNames = (
|
|||
|
||||
export const getValidCountryCodeOfCountryNames: (
|
||||
countryNames: ReadonlyArray<string>,
|
||||
) => Option<ReadonlyArray<CountryCode>> = unimplemented;
|
||||
) => Option<ReadonlyArray<CountryCode>> = option.traverseArray(getCountryCode);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// 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
|
||||
// computation with it, replaces this number in the database by the result of
|
||||
// the computation and returns it
|
||||
const createSimulatedAsyncMethod = (): ((toAdd: number) => Task<number>) => {
|
||||
const createSimulatedAsyncMethod = (): (toAdd: number) => Task<number> => {
|
||||
let number = 0;
|
||||
|
||||
return (toAdd: number) => async () => {
|
||||
|
@ -186,7 +199,9 @@ const createSimulatedAsyncMethod = (): ((toAdd: number) => Task<number>) => {
|
|||
export const simulatedAsyncMethodForParallel = createSimulatedAsyncMethod();
|
||||
export const performAsyncComputationInParallel: (
|
||||
numbers: ReadonlyArray<number>,
|
||||
) => Task<ReadonlyArray<number>> = unimplementedAsync;
|
||||
) => Task<ReadonlyArray<number>> = task.traverseArray(
|
||||
simulatedAsyncMethodForParallel,
|
||||
);
|
||||
|
||||
// Write a method to traverse an array by running the method
|
||||
// `simulatedAsyncMethodForSequence: (toAdd: number) => Task<number>`
|
||||
|
@ -198,7 +213,9 @@ export const performAsyncComputationInParallel: (
|
|||
export const simulatedAsyncMethodForSequence = createSimulatedAsyncMethod();
|
||||
export const performAsyncComputationInSequence: (
|
||||
numbers: ReadonlyArray<number>,
|
||||
) => Task<ReadonlyArray<number>> = unimplementedAsync;
|
||||
) => Task<ReadonlyArray<number>> = task.traverseSeqArray(
|
||||
simulatedAsyncMethodForSequence,
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// SEQUENCE //
|
||||
|
@ -220,11 +237,11 @@ export const performAsyncComputationInSequence: (
|
|||
|
||||
export const sequenceOptionTask: (
|
||||
optionOfTask: Option<Task<Currency>>,
|
||||
) => Task<Option<Currency>> = unimplementedAsync;
|
||||
) => Task<Option<Currency>> = option.sequence(task.ApplicativePar);
|
||||
|
||||
export const sequenceOptionArray: (
|
||||
arrayOfOptions: ReadonlyArray<Option<CountryCode>>,
|
||||
) => Option<ReadonlyArray<CountryCode>> = unimplemented;
|
||||
) => Option<ReadonlyArray<CountryCode>> = option.sequenceArray;
|
||||
|
||||
// BONUS: try using these two functions in the exercises 'TRAVERSING OPTIONS'
|
||||
// and 'TRAVERSING ARRAYS' above
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
// `fp-ts` training Exercise 6
|
||||
// Introduction to `ReaderTaskEither`
|
||||
|
||||
import { ReaderTaskEither } from 'fp-ts/lib/ReaderTaskEither';
|
||||
import { unimplemented } from '../utils';
|
||||
import { Application } from './application';
|
||||
import { User } from './domain';
|
||||
import { ReaderTaskEither } from "fp-ts/lib/ReaderTaskEither";
|
||||
import { Application } from "./application";
|
||||
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
|
||||
// `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
|
||||
// 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: {
|
||||
userId: string;
|
||||
}) => ReaderTaskEither<
|
||||
User.Repository.Access,
|
||||
User.Repository.UserNotFoundError,
|
||||
string
|
||||
> = unimplemented;
|
||||
> = ({ userId }) =>
|
||||
pipe(
|
||||
User.Repository.getById(userId),
|
||||
rte.map(capitalizeUserName),
|
||||
);
|
||||
|
||||
// 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.
|
||||
|
@ -52,7 +60,13 @@ export const getConcatenationOfTheTwoUserNames: (args: {
|
|||
User.Repository.Access,
|
||||
User.Repository.UserNotFoundError,
|
||||
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
|
||||
// only retrieve after performing some operations, in other words, operations
|
||||
|
@ -69,7 +83,16 @@ export const getConcatenationOfTheBestFriendNameAndUserName: (args: {
|
|||
User.Repository.Access,
|
||||
User.Repository.UserNotFoundError,
|
||||
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.
|
||||
// The challenge of this usecase is to use TimeService in the flow of our `rte`
|
||||
|
@ -81,4 +104,13 @@ export const getConcatenationOfUserNameAndCurrentYear: (args: {
|
|||
Dependencies,
|
||||
User.Repository.UserNotFoundError,
|
||||
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
|
||||
// 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
|
||||
// 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
|
||||
// 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`.
|
||||
// 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`
|
||||
// instance.
|
||||
|
||||
export const numberArrayFromSet: ReadonlyArray<number> = unimplemented();
|
||||
export const numberArrayFromSet: ReadonlyArray<number> = readonlySet
|
||||
.toReadonlyArray(number.Ord)(numberSet);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// MAP //
|
||||
|
@ -73,11 +84,11 @@ export const numberArrayFromSet: ReadonlyArray<number> = unimplemented();
|
|||
// numbers, etc...)
|
||||
|
||||
export const associativeArray: ReadonlyArray<[number, string]> = [
|
||||
[1, 'Alice'],
|
||||
[2, 'Bob'],
|
||||
[3, 'Clara'],
|
||||
[4, 'Denise'],
|
||||
[2, 'Robert'],
|
||||
[1, "Alice"],
|
||||
[2, "Bob"],
|
||||
[3, "Clara"],
|
||||
[4, "Denise"],
|
||||
[2, "Robert"],
|
||||
];
|
||||
|
||||
// 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
|
||||
// `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
|
||||
// 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
|
||||
// helpful in defining `mapWithLastEntry`?
|
||||
|
||||
export const mapWithConcatenatedEntries: ReadonlyMap<number, string> =
|
||||
unimplemented();
|
||||
export const mapWithConcatenatedEntries: ReadonlyMap<number, string> = pipe(
|
||||
associativeArray,
|
||||
readonlyMap.fromFoldable(
|
||||
number.Eq,
|
||||
string.Semigroup,
|
||||
readonlyArray.Foldable,
|
||||
),
|
||||
);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// DIFFERENCE / UNION / INTERSECTION //
|
||||
|
@ -137,12 +161,18 @@ export const odds = new Set([1, 3, 5, 7, 9]);
|
|||
// HINT:
|
||||
// - 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
|
||||
// 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(
|
||||
[
|
||||
{ page: 'home', views: 5 },
|
||||
{ page: 'about', views: 2 },
|
||||
{ page: 'blog', views: 7 },
|
||||
].map(entry => [entry.page, entry]),
|
||||
{ page: "home", views: 5 },
|
||||
{ page: "about", views: 2 },
|
||||
{ page: "blog", views: 7 },
|
||||
].map((entry) => [entry.page, entry]),
|
||||
);
|
||||
|
||||
export const pageViewsB = new Map(
|
||||
[
|
||||
{ page: 'home', views: 10 },
|
||||
{ page: 'blog', views: 35 },
|
||||
{ page: 'faq', views: 5 },
|
||||
].map(entry => [entry.page, entry]),
|
||||
{ page: "home", views: 10 },
|
||||
{ page: "blog", views: 35 },
|
||||
{ page: "faq", views: 5 },
|
||||
].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
|
||||
// of analytics `pageViewsA` and `pageViewsB`.
|
||||
//
|
||||
// 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
|
||||
// appear in both sources of analytics `pageViewsA` and `pageViewsB`.
|
||||
|
||||
export const intersectionPageViews: ReadonlyMap<string, Analytics> =
|
||||
unimplemented();
|
||||
export const intersectionPageViews: ReadonlyMap<string, Analytics> = pipe(
|
||||
pageViewsA,
|
||||
readonlyMap.intersection(string.Eq, AnalyticsSemigroup)(
|
||||
pageViewsB,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
// `fp-ts` training Exercise 8
|
||||
// Define your own combinators
|
||||
|
||||
import { Either } from 'fp-ts/Either';
|
||||
import { ReaderTaskEither } from 'fp-ts/ReaderTaskEither';
|
||||
import { unimplemented } from '../utils';
|
||||
import { Either } from "fp-ts/Either";
|
||||
import { ReaderTaskEither } from "fp-ts/ReaderTaskEither";
|
||||
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,
|
||||
// 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,
|
||||
E,
|
||||
{ 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
|
||||
// "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
|
||||
// `apSEitherKW`.
|
||||
|
@ -89,9 +99,27 @@ export const bindEitherKW = unimplemented;
|
|||
// - remember that "widen" in the case of `Either` means the union of the
|
||||
// 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
|
||||
// `bindReaderKW`.
|
||||
|
@ -100,6 +128,24 @@ export const apSEitherKW = unimplemented;
|
|||
// - remember that "widen" in the case of `Reader` means the intersection of
|
||||
// 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