🎉 Initial commit
This commit is contained in:
commit
63f1f160ab
13 changed files with 4323 additions and 0 deletions
39
.eslintrc.js
Normal file
39
.eslintrc.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
extends: [
|
||||
'plugin:fp/recommended',
|
||||
],
|
||||
plugins: ['fp', 'inato'],
|
||||
rules: {
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars-experimental': ['error'],
|
||||
'no-useless-constructor': 'off',
|
||||
'@typescript-eslint/no-useless-constructor': ['error'],
|
||||
'@typescript-eslint/camelcase': [
|
||||
'error',
|
||||
{
|
||||
properties: 'never',
|
||||
},
|
||||
],
|
||||
'react-hooks/rules-of-hooks': 'off',
|
||||
'import/extensions': [
|
||||
'error',
|
||||
{
|
||||
ts: 'never',
|
||||
},
|
||||
],
|
||||
'fp/no-throw':'off',
|
||||
'fp/no-class': 'off',
|
||||
'fp/no-mutation': 'off',
|
||||
'fp/no-nil': 'off',
|
||||
'fp/no-this': 'off',
|
||||
'fp/no-unused-expression': 'off',
|
||||
'inato/no-factories-outside-of-tests': 'error',
|
||||
},
|
||||
};
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
node_modules
|
||||
dist
|
||||
yarn-error.log*
|
||||
|
||||
.DS_Store
|
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
@ -0,0 +1 @@
|
|||
v12.18.0
|
4
.prettierrc
Normal file
4
.prettierrc
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all"
|
||||
}
|
15
.vscode/settings.json
vendored
Normal file
15
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"search.exclude": {
|
||||
"**/node_modules/": true
|
||||
},
|
||||
"eslint.packageManager": "yarn",
|
||||
"eslint.alwaysShowStatus": true,
|
||||
"javascript.validate.enable": false,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"prettier.configPath": ".prettierrc",
|
||||
"eslint.format.enable": true
|
||||
}
|
5
jest.config.js
Normal file
5
jest.config.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
testPathIgnorePatterns: ['/dist/', '/node_modules/'],
|
||||
};
|
30
package.json
Normal file
30
package.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "fp-ts-training",
|
||||
"version": "1.0.0",
|
||||
"description": "Learning fp-ts while having fun",
|
||||
"author": "Hugo Saracino <hugo@inato.com>",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/inato/fp-ts-training.git"
|
||||
},
|
||||
"homepage": "https://github.com/inato/fp-ts-training",
|
||||
"scripts": {
|
||||
"build": "yarn tsc -b",
|
||||
"test": "jest --runInBand"
|
||||
},
|
||||
"dependencies": {
|
||||
"fp-ts": "^2.6.7",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^26.0.3",
|
||||
"@types/node": "^14.0.14",
|
||||
"eslint": "^7.3.1",
|
||||
"eslint-plugin-fp": "^2.3.0",
|
||||
"jest": "^26.1.0",
|
||||
"ts-jest": "^26.1.1",
|
||||
"typescript": "^3.9.6"
|
||||
}
|
||||
}
|
71
src/exo1/exo1.test.ts
Normal file
71
src/exo1/exo1.test.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import * as Either from 'fp-ts/lib/Either';
|
||||
import * as Option from 'fp-ts/lib/Option';
|
||||
|
||||
import {
|
||||
divide,
|
||||
DivisionByZero,
|
||||
safeDivide,
|
||||
safeDivideWithError,
|
||||
asyncDivide,
|
||||
asyncSafeDivideWithError,
|
||||
} from './exo1';
|
||||
|
||||
describe('exo1', () => {
|
||||
describe('divide', () => {
|
||||
it('should return the result of dividing two numbers', () => {
|
||||
expect(divide(25, 5)).toEqual(5);
|
||||
});
|
||||
|
||||
it('should return Infinity if the denominator is zero', () => {
|
||||
expect(divide(25, 0)).toBe(Infinity);
|
||||
});
|
||||
});
|
||||
|
||||
describe('safeDivide', () => {
|
||||
it('should return the result of dividing two numbers', () => {
|
||||
expect(safeDivide(25, 5)).toStrictEqual(Option.some(5));
|
||||
});
|
||||
|
||||
it('should return Option.none if the denominator is zero', () => {
|
||||
expect(safeDivide(25, 0)).toStrictEqual(Option.none);
|
||||
});
|
||||
});
|
||||
|
||||
describe('safeDivideWithError', () => {
|
||||
it('should return the result of dividing two numbers', () => {
|
||||
expect(safeDivideWithError(25, 5)).toStrictEqual(Either.right(5));
|
||||
});
|
||||
|
||||
it('should return Either.left(DivisionByZero) if the denominator is zero', () => {
|
||||
expect(safeDivideWithError(25, 0)).toStrictEqual(
|
||||
Either.left(DivisionByZero),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncDivide', () => {
|
||||
it('should eventually return the result of dividing two numbers', async () => {
|
||||
const result = await asyncDivide(25, 5);
|
||||
|
||||
expect(result).toEqual(5);
|
||||
});
|
||||
|
||||
it('should eventually return Infinity if the denominator is zero', async () => {
|
||||
await expect(asyncDivide(25, 0)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe('asyncSafeDivideWithError', () => {
|
||||
it('should eventually return the result of dividing two numbers', async () => {
|
||||
const result = await asyncSafeDivideWithError(25, 5)();
|
||||
|
||||
expect(result).toStrictEqual(Either.right(5));
|
||||
});
|
||||
|
||||
it('should eventually return Either.left(DivisionByZero) if the denominator is zero', async () => {
|
||||
const result = await asyncSafeDivideWithError(25, 0)();
|
||||
|
||||
expect(result).toStrictEqual(Either.left(DivisionByZero));
|
||||
});
|
||||
});
|
||||
});
|
83
src/exo1/exo1.ts
Normal file
83
src/exo1/exo1.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
// `fp-ts` training Exercice 1
|
||||
// Basic types:
|
||||
// - Option
|
||||
// - Either
|
||||
// - TaskEither
|
||||
|
||||
import * as Option from 'fp-ts/lib/Option';
|
||||
import * as Either from 'fp-ts/lib/Either';
|
||||
import * as TaskEither from 'fp-ts/lib/TaskEither';
|
||||
|
||||
import { unimplemented, sleep, unimplementedAsync } from '../utils';
|
||||
|
||||
export const divide = (a: number, b: number): number => {
|
||||
return a / b;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// OPTION //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Write the safe version of `divide` whith signature:
|
||||
// safeDivide : (a: number, b: number) => Option<number>
|
||||
//
|
||||
// HINT: Option has two basic contructors:
|
||||
// - `Option.some(value)`
|
||||
// - `Option.none`
|
||||
|
||||
export const safeDivide: (
|
||||
a: number,
|
||||
b: number,
|
||||
) => Option.Option<number> = unimplemented;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// EITHER //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Write the safe version of `divide` whith signature:
|
||||
// safeDivideWithError : (a: number, b: number) => Either<DivideByZeroError, number>
|
||||
//
|
||||
// BONUS POINT: Implement `safeDivideWithError` in terms of `safeDivide`.
|
||||
//
|
||||
// HINT : Either has two basic constructors:
|
||||
// - `Either.left(leftValue)`
|
||||
// - `Either.right(rightValue)`
|
||||
// as well as "smarter" constructors like:
|
||||
// - `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 const safeDivideWithError: (
|
||||
a: number,
|
||||
b: number,
|
||||
) => Either.Either<DivisionByZeroError, number> = unimplemented;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// TASKEITHER //
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Now let's say we have a (pretend) API call that will perform the division for us
|
||||
// (throwing an error when the denominator is 0)
|
||||
export const asyncDivide = async (a: number, b: number) => {
|
||||
await sleep(1000);
|
||||
|
||||
if (b === 0) {
|
||||
throw new Error('BOOM!');
|
||||
}
|
||||
|
||||
return a / b;
|
||||
};
|
||||
|
||||
// Write the safe version of `divide` whith signature:
|
||||
// asyncSafeDivideWithError : (a: number, b: number) => TaskEither<DivideByZeroError, number>
|
||||
//
|
||||
// HINT: TaskEither has a special constructor to transform a Promise<T> into
|
||||
// a TaskEither<Error, T>:
|
||||
// - `TaskEither.tryCatch(onReject: err => leftValue, onResolve: val => rightValue)(promise)`
|
||||
|
||||
export const asyncSafeDivideWithError: (
|
||||
a: number,
|
||||
b: number,
|
||||
) => TaskEither.TaskEither<DivisionByZeroError, number> = unimplementedAsync;
|
1
src/exo1/index.ts
Normal file
1
src/exo1/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './exo1';
|
6
src/utils.ts
Normal file
6
src/utils.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export const unimplemented = () => undefined as any;
|
||||
export const unimplementedAsync = () => () => undefined as any;
|
||||
|
||||
export function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"module": "commonjs",
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "es2019",
|
||||
"lib": ["es2019", "es2020.string", "es2020.symbol.wellknown"],
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitAny": true,
|
||||
"resolveJsonModule": true,
|
||||
"outDir": "./dist",
|
||||
"rootDirs": ["src"],
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"sourceRoot": "/",
|
||||
"noEmitHelpers": true,
|
||||
"importHelpers": true,
|
||||
"typeRoots": ["node_modules/@types", "src/types"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
Reference in a new issue