├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── src ├── employee.model.ts ├── employee.repository.ts ├── index.html ├── main.ts └── maybe.monad.ts ├── tsconfig.json └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | blog/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # typescript-monads 2 | Accompanying repository for codewithstyle.info articles about monads 3 | 4 | # Running the code 5 | Clone the repository and run the following commands: 6 | ``` 7 | npm install 8 | npm start 9 | ``` 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monads", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "webpack", 8 | "build:prod": "webpack -p", 9 | "watch": "webpack --watch", 10 | "start": "./node_modules/.bin/webpack-dev-server" 11 | }, 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "awesome-typescript-loader": "^3.4.1", 16 | "html-webpack-plugin": "^2.30.1", 17 | "typescript": "^2.7.1", 18 | "webpack": "^3.11.0", 19 | "webpack-dev-server": "^2.11.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/employee.model.ts: -------------------------------------------------------------------------------- 1 | import { Maybe } from "./maybe.monad"; 2 | 3 | export interface Employee { 4 | id: number; 5 | name: string; 6 | supervisorId: Maybe; 7 | } -------------------------------------------------------------------------------- /src/employee.repository.ts: -------------------------------------------------------------------------------- 1 | import { Employee } from "./employee.model"; 2 | import { Maybe } from "./maybe.monad"; 3 | 4 | export class EmployeeRepository { 5 | private employees: Employee[] = [ 6 | { id: 1, name: "John", supervisorId: Maybe.none() }, 7 | { id: 2, name: "Jane", supervisorId: Maybe.some(1) }, 8 | { id: 3, name: "Joe", supervisorId: Maybe.some(2) }, 9 | ]; 10 | 11 | findById(id: number): Maybe { 12 | const results = this.employees.filter(employee => employee.id === id); 13 | return results.length ? Maybe.some(results[0]) : Maybe.none(); 14 | } 15 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Find employee's supervisor

10 |

11 | 12 | 13 |

14 |

15 | 16 |

17 |

18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { EmployeeRepository } from "./employee.repository"; 2 | import { Maybe } from "./maybe.monad"; 3 | 4 | const employeeIdInputEl = document.getElementById("employeeIdInput") as HTMLInputElement; 5 | const findEmployeeButtonEl = document.getElementById("findEmployeeButton"); 6 | const searchResultsEl = document.getElementById("searchResults"); 7 | 8 | const repository = new EmployeeRepository(); 9 | 10 | findEmployeeButtonEl.addEventListener("click", () => { 11 | const supervisorName = getSupervisorName(Maybe.fromValue(employeeIdInputEl.value)); 12 | searchResultsEl.innerText = `Supervisor name: ${supervisorName.getOrElse("could not find")}`; 13 | }); 14 | 15 | function getSupervisorName(maybeEnteredId: Maybe): Maybe { 16 | return Maybe.run(function* () { 17 | const enteredIdStr = yield maybeEnteredId; 18 | const enteredId = parseInt(enteredIdStr); 19 | const employee = yield repository.findById(enteredId); 20 | const supervisorId = yield employee.supervisorId; 21 | const supervisor = yield repository.findById(supervisorId); 22 | return Maybe.some(supervisor.name); 23 | }()); 24 | } 25 | 26 | function* numbers(): IterableIterator { 27 | console.log('Inside numbers; start');1 28 | yield 1; 29 | console.log('Inside numbers; after first yield'); 30 | yield 2; 31 | console.log('Inside numbers; end'); 32 | } 33 | 34 | const numbersGenerator = numbers(); 35 | console.log('Outside of numbers'); 36 | console.log(numbersGenerator.next()); 37 | console.log('Outside of numbers; after first next'); 38 | console.log(numbersGenerator.next()); 39 | console.log('Outside of numbers; after second next'); -------------------------------------------------------------------------------- /src/maybe.monad.ts: -------------------------------------------------------------------------------- 1 | export class Maybe { 2 | private constructor(private value: T | null) {} 3 | 4 | static some(value: T) { 5 | if (!value) { 6 | throw Error("Provided value must not be empty"); 7 | } 8 | return new Maybe(value); 9 | } 10 | 11 | static none() { 12 | return new Maybe(null); 13 | } 14 | 15 | static fromValue(value: T) { 16 | return value ? Maybe.some(value) : Maybe.none(); 17 | } 18 | 19 | static run(gen: IterableIterator>): Maybe { 20 | function step(value?) { 21 | const result = gen.next(value); 22 | if (result.done) { 23 | return result.value; 24 | } 25 | return result.value.flatMap(step); 26 | } 27 | return step(); 28 | } 29 | 30 | map(f: (wrapped: T) => R): Maybe { 31 | if (this.value === null) { 32 | return Maybe.none(); 33 | } else { 34 | return Maybe.some(f(this.value)); 35 | } 36 | } 37 | 38 | flatMap(f: (wrapped: T) => Maybe): Maybe { 39 | if (this.value === null) { 40 | return Maybe.none(); 41 | } else { 42 | return f(this.value); 43 | } 44 | } 45 | 46 | getOrElse(defaultValue: T) { 47 | return this.value === null ? defaultValue : this.value; 48 | } 49 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | "lib": [ "dom", "es2015" ], /* Specify library files to be included in the compilation: */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 12 | // "outFile": "./", /* Concatenate and emit output to single file. */ 13 | // "outDir": "./", /* Redirect output structure to the directory. */ 14 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 15 | // "removeComments": true, /* Do not emit comments to output. */ 16 | // "noEmit": true, /* Do not emit outputs. */ 17 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 18 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 19 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 20 | 21 | /* Strict Type-Checking Options */ 22 | "strict": false /* Enable all strict type-checking options. */ 23 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 24 | // "strictNullChecks": true, /* Enable strict null checks. */ 25 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 26 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 27 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 28 | 29 | /* Additional Checks */ 30 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 31 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 32 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 33 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 34 | 35 | /* Module Resolution Options */ 36 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 37 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 38 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 39 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 40 | // "typeRoots": [], /* List of folders to include type definitions from. */ 41 | // "types": [], /* Type declaration files to be included in compilation. */ 42 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 43 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 44 | 45 | /* Source Map Options */ 46 | // "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 47 | // "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */ 48 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 49 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 50 | 51 | /* Experimental Options */ 52 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 53 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 54 | } 55 | } -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | const config = { 5 | entry: './main.ts', 6 | resolve: { 7 | extensions: ['.ts', '.tsx', '.js', '.jsx'] 8 | }, 9 | devtool: 'source-map', 10 | output: { 11 | filename: 'bundle.js', 12 | path: path.resolve(__dirname, 'dist') 13 | }, 14 | context: path.resolve(__dirname, "src"), 15 | module: { 16 | loaders: [ 17 | { 18 | test: /\.tsx?$/, 19 | loader: 'awesome-typescript-loader' 20 | } 21 | ] 22 | }, 23 | plugins: [ 24 | new HtmlWebpackPlugin({ 25 | template: 'index.html' 26 | }) 27 | ], 28 | }; 29 | module.exports = config; --------------------------------------------------------------------------------