🎉 Initial commit

This commit is contained in:
Hugo Saracino 2020-07-03 18:01:45 +02:00
commit 63f1f160ab
13 changed files with 4323 additions and 0 deletions

39
.eslintrc.js Normal file
View 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
View file

@ -0,0 +1,5 @@
node_modules
dist
yarn-error.log*
.DS_Store

1
.nvmrc Normal file
View file

@ -0,0 +1 @@
v12.18.0

4
.prettierrc Normal file
View file

@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

15
.vscode/settings.json vendored Normal file
View 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
View file

@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['/dist/', '/node_modules/'],
};

30
package.json Normal file
View 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
View 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
View 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
View file

@ -0,0 +1 @@
export * from './exo1';

6
src/utils.ts Normal file
View 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
View 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"]
}

4033
yarn.lock Normal file

File diff suppressed because it is too large Load diff