├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── JavaScript ├── 1-imperative-bad.js ├── 2-imperative-good.js ├── 3-functional-bad.js ├── 4-functional-good.js ├── 5-functional-alternative.js ├── 6-functional-pure.js ├── 7-mixed.js ├── 8-declarative.js └── cities.csv ├── LICENSE └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | 10 | [{*.js,*.mjs,*.ts,*.json,*.yml}] 11 | indent_size = 2 12 | indent_style = space 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": "latest" 10 | }, 11 | "globals": { 12 | "BigInt": true 13 | }, 14 | "rules": { 15 | "indent": [ 16 | "error", 17 | 2 18 | ], 19 | "linebreak-style": [ 20 | "error", 21 | "unix" 22 | ], 23 | "quotes": [ 24 | "error", 25 | "single" 26 | ], 27 | "semi": [ 28 | "error", 29 | "always" 30 | ], 31 | "no-loop-func": [ 32 | "error" 33 | ], 34 | "block-spacing": [ 35 | "error", 36 | "always" 37 | ], 38 | "camelcase": [ 39 | "error" 40 | ], 41 | "eqeqeq": [ 42 | "error", 43 | "always" 44 | ], 45 | "strict": [ 46 | "error", 47 | "global" 48 | ], 49 | "brace-style": [ 50 | "error", 51 | "1tbs", 52 | { 53 | "allowSingleLine": true 54 | } 55 | ], 56 | "comma-style": [ 57 | "error", 58 | "last" 59 | ], 60 | "comma-spacing": [ 61 | "error", 62 | { 63 | "before": false, 64 | "after": true 65 | } 66 | ], 67 | "eol-last": [ 68 | "error" 69 | ], 70 | "func-call-spacing": [ 71 | "error", 72 | "never" 73 | ], 74 | "key-spacing": [ 75 | "error", 76 | { 77 | "beforeColon": false, 78 | "afterColon": true, 79 | "mode": "minimum" 80 | } 81 | ], 82 | "keyword-spacing": [ 83 | "error", 84 | { 85 | "before": true, 86 | "after": true, 87 | "overrides": { 88 | "function": { 89 | "after": false 90 | } 91 | } 92 | } 93 | ], 94 | "max-len": [ 95 | "error", 96 | { 97 | "code": 80, 98 | "ignoreUrls": true 99 | } 100 | ], 101 | "max-nested-callbacks": [ 102 | "error", 103 | { 104 | "max": 7 105 | } 106 | ], 107 | "new-cap": [ 108 | "error", 109 | { 110 | "newIsCap": true, 111 | "capIsNew": false, 112 | "properties": true 113 | } 114 | ], 115 | "new-parens": [ 116 | "error" 117 | ], 118 | "no-lonely-if": [ 119 | "error" 120 | ], 121 | "no-trailing-spaces": [ 122 | "error" 123 | ], 124 | "no-unneeded-ternary": [ 125 | "error" 126 | ], 127 | "no-whitespace-before-property": [ 128 | "error" 129 | ], 130 | "object-curly-spacing": [ 131 | "error", 132 | "always" 133 | ], 134 | "operator-assignment": [ 135 | "error", 136 | "always" 137 | ], 138 | "operator-linebreak": [ 139 | "error", 140 | "after" 141 | ], 142 | "semi-spacing": [ 143 | "error", 144 | { 145 | "before": false, 146 | "after": true 147 | } 148 | ], 149 | "space-before-blocks": [ 150 | "error", 151 | "always" 152 | ], 153 | "space-before-function-paren": [ 154 | "error", 155 | { 156 | "anonymous": "never", 157 | "named": "never", 158 | "asyncArrow": "always" 159 | } 160 | ], 161 | "space-in-parens": [ 162 | "error", 163 | "never" 164 | ], 165 | "space-infix-ops": [ 166 | "error" 167 | ], 168 | "space-unary-ops": [ 169 | "error", 170 | { 171 | "words": true, 172 | "nonwords": false, 173 | "overrides": { 174 | "typeof": false 175 | } 176 | } 177 | ], 178 | "no-unreachable": [ 179 | "error" 180 | ], 181 | "no-global-assign": [ 182 | "error" 183 | ], 184 | "no-self-compare": [ 185 | "error" 186 | ], 187 | "no-unmodified-loop-condition": [ 188 | "error" 189 | ], 190 | "no-constant-condition": [ 191 | "error", 192 | { 193 | "checkLoops": false 194 | } 195 | ], 196 | "no-console": [ 197 | "off" 198 | ], 199 | "no-useless-concat": [ 200 | "error" 201 | ], 202 | "no-useless-escape": [ 203 | "error" 204 | ], 205 | "no-shadow-restricted-names": [ 206 | "error" 207 | ], 208 | "no-use-before-define": [ 209 | "error", 210 | { 211 | "functions": false 212 | } 213 | ], 214 | "arrow-parens": [ 215 | "error", 216 | "always" 217 | ], 218 | "arrow-body-style": [ 219 | "error", 220 | "as-needed" 221 | ], 222 | "arrow-spacing": [ 223 | "error" 224 | ], 225 | "no-confusing-arrow": [ 226 | "error", 227 | { 228 | "allowParens": true 229 | } 230 | ], 231 | "no-useless-computed-key": [ 232 | "error" 233 | ], 234 | "no-useless-rename": [ 235 | "error" 236 | ], 237 | "no-var": [ 238 | "error" 239 | ], 240 | "object-shorthand": [ 241 | "error", 242 | "always" 243 | ], 244 | "prefer-arrow-callback": [ 245 | "error" 246 | ], 247 | "prefer-const": [ 248 | "error" 249 | ], 250 | "prefer-numeric-literals": [ 251 | "error" 252 | ], 253 | "prefer-rest-params": [ 254 | "error" 255 | ], 256 | "prefer-spread": [ 257 | "error" 258 | ], 259 | "rest-spread-spacing": [ 260 | "error", 261 | "never" 262 | ], 263 | "template-curly-spacing": [ 264 | "error", 265 | "never" 266 | ], 267 | "consistent-return": [ 268 | "error", 269 | { "treatUndefinedAsUnspecified": true } 270 | ] 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /JavaScript/1-imperative-bad.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Bad practices in imperative programming below: 4 | // 1. Single execution sequence, no procedural decomposition 5 | // 2. No appropriate error handling (error ignoring) 6 | // 3. Unnecessary variables and steps (flags, calculations) 7 | // 4. Unclear naming (or extra short) or no naming (array indexes) 8 | // 5. Long expressions 9 | 10 | const fs = require('node:fs'); 11 | 12 | const data = fs.readFileSync('./cities.csv', 'utf8'); 13 | if (data) { 14 | const lines = data.split('\n'); 15 | lines.pop(); 16 | const table = []; 17 | let first = true; 18 | let max = 0; 19 | for (const line of lines) { 20 | if (first) { 21 | first = false; 22 | } else { 23 | const cells = line.split(','); 24 | const d = parseInt(cells[3]); 25 | if (d > max) max = d; 26 | table.push([cells[0], cells[1], cells[2], cells[3], cells[4]]); 27 | } 28 | } 29 | for (const row of table) { 30 | const a = Math.round(row[3] * 100 / max); 31 | row.push(a.toString()); 32 | } 33 | table.sort((r1, r2) => r2[5] - r1[5]); 34 | for (const row of table) { 35 | let s = row[0].padEnd(18); 36 | s += row[1].padStart(10); 37 | s += row[2].padStart(8); 38 | s += row[3].padStart(8); 39 | s += row[4].padStart(18); 40 | s += row[5].padStart(6); 41 | console.log(s); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /JavaScript/2-imperative-good.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 1. Control flow 4 | // 2. Step by step execution 5 | // 3. Assignment statements 6 | // 4. Loops and conditions: for/if 7 | // 5. State variables and side effects 8 | // 6. Mutable data structures 9 | // 7. Model of the process 10 | 11 | const fs = require('node:fs'); 12 | 13 | const loadFile = (fileName) => { 14 | try { 15 | const data = fs.readFileSync(fileName, 'utf8'); 16 | return data; 17 | } catch (error) { 18 | console.log('Can\'t read file: ' + fileName); 19 | } 20 | return null; 21 | }; 22 | 23 | const parseFile = (data) => { 24 | const lines = data.split('\n'); 25 | lines.shift(); 26 | const cities = []; 27 | for (const line of lines) { 28 | if (line) { 29 | const cells = line.split(','); 30 | const [name, population, area, density, country] = cells; 31 | cities.push({ 32 | name, 33 | population: parseInt(population), 34 | area: parseInt(area), 35 | density: parseInt(density), 36 | country, 37 | }); 38 | } 39 | } 40 | return cities; 41 | }; 42 | 43 | const calculateDensityColumn = (cities) => { 44 | cities.sort((city1, city2) => city2.density - city1.density); 45 | const maxDensity = cities[0].density; 46 | for (const city of cities) { 47 | city.relative = Math.round(city.density * 100 / maxDensity); 48 | } 49 | }; 50 | 51 | const showTable = (cities) => { 52 | for (const city of cities) { 53 | const line = ( 54 | city.name.padEnd(18) + 55 | city.population.toString().padStart(10) + 56 | city.area.toString().padStart(8) + 57 | city.density.toString().padStart(8) + 58 | city.country.padStart(18) + 59 | city.relative.toString().padStart(6) 60 | ); 61 | console.log(line); 62 | } 63 | }; 64 | 65 | const data = loadFile('./cities.csv'); 66 | if (data) { 67 | const cities = parseFile(data); 68 | calculateDensityColumn(cities); 69 | showTable(cities); 70 | } 71 | -------------------------------------------------------------------------------- /JavaScript/3-functional-bad.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Bad practices in FP below: 4 | // 1. Global data structures 5 | // 2. Mutable global state 6 | // 3. Long anonymous lambdas 7 | // 4. Complex syntactic hacks 8 | // 5. Assignment statements 9 | // 6. Return hack 10 | // 7. Return hack 11 | // 8. Magic numbers 12 | 13 | const fs = require('node:fs'); 14 | 15 | const rpad = (s, count, char) => s.padEnd(count, char); 16 | const lpad = (s, count, char) => s.padStart(count, char); 17 | 18 | const padding = [rpad, lpad, lpad, lpad, lpad, lpad]; 19 | const width = [18, 10, 8, 8, 18, 6]; 20 | 21 | let maxDensity = 0; 22 | 23 | const format = (file) => ( 24 | fs.readFileSync(file, 'utf8').split('\n') 25 | .filter((s, i) => i && s) 26 | .map((line) => line.split(',').map((cell, i, arr) => ( 27 | (i < 1 || i > 3) || (cell = parseInt(cell), arr[i] = cell), 28 | (i - 3) || (maxDensity = maxDensity > cell ? maxDensity : cell), cell 29 | ))) 30 | .map((row) => ( 31 | row.push(Math.round(row[3] * 100 / maxDensity).toString()), row 32 | )) 33 | .sort((r1, r2) => (r2[5] - r1[5])) 34 | .map((row) => ( 35 | row.map((cell, i) => padding[i](cell + '', width[i])).join('') 36 | )) 37 | .join('\n') 38 | ); 39 | 40 | console.log(format('./cities.csv')); 41 | -------------------------------------------------------------------------------- /JavaScript/4-functional-good.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // FP paradigm: 4 | // 1. Equations instead of control flow 5 | // 2. Lazy calculations 6 | // 3. Parallel execution ready 7 | // 4. Recursion instead of loops 8 | // 5. Immutability, no state 9 | // 6. No variables, no side effects 10 | // 7. Math model 11 | 12 | const fs = require('node:fs'); 13 | 14 | const proportion = (max, val) => Math.round(val * 100 / max); 15 | const compose = (...funcs) => (x) => funcs.reduce((x, fn) => fn(x), x); 16 | 17 | const cellPad = (i, s, width) => (i ? s.padStart(width) : s.padEnd(width)); 18 | const cellWidth = (i) => [18, 10, 8, 8, 18, 6][i]; 19 | 20 | const renderCell = (cell, i) => cellPad(i, cell + '', cellWidth(i)); 21 | const renderRow = (row) => row.map(renderCell).join(''); 22 | const renderTable = (table) => table.map(renderRow).join('\n'); 23 | 24 | const densityCol = () => 3; 25 | const sortByDensity = (table) => table.sort( 26 | (row1, row2) => (row2[densityCol()] - row1[densityCol()]) 27 | ); 28 | const calcColumn = (table, max) => table.map( 29 | (row) => (row.push(proportion(max, row[densityCol()])), row) 30 | ); 31 | const calcProportion = (table) => calcColumn(table, table[0][densityCol()]); 32 | 33 | const parseTable = (lines) => lines.map((line) => line.split(',')); 34 | const toLines = (data) => data.split('\n').filter((s, i) => i && s); 35 | const readFile = (file) => fs.readFileSync(file, 'utf8'); 36 | const getDataset = compose(readFile, toLines, parseTable); 37 | 38 | const main = compose(getDataset, sortByDensity, calcProportion, renderTable); 39 | 40 | console.log(main('./cities.csv')); 41 | -------------------------------------------------------------------------------- /JavaScript/5-functional-alternative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('node:fs'); 4 | 5 | const proportion = (max, val) => Math.round(val * 100 / max); 6 | 7 | const compose = (...funcs) => (x) => funcs.reduce((x, fn) => fn(x), x); 8 | 9 | const densityCol = 3; 10 | const cellWidth = [18, 10, 8, 8, 18, 6]; 11 | 12 | const renderTable = (table) => table 13 | .map((row) => row 14 | .map((cell, i) => cell 15 | .toString()[(i ? 'padStart' : 'padEnd')](cellWidth[i])) 16 | .join('')) 17 | .join('\n'); 18 | 19 | const calcProportion = (table) => ( 20 | table.sort((row1, row2) => (row2[densityCol] - row1[densityCol])), 21 | table.map((row) => ( 22 | row.push(proportion(table[0][densityCol], row[densityCol])), row 23 | )) 24 | ); 25 | 26 | const getDataset = (file) => fs.readFileSync(file, 'utf8') 27 | .split('\n') 28 | .filter((s, i) => i && s) 29 | .map((line) => line.split(',')); 30 | 31 | const main = compose(getDataset, calcProportion, renderTable); 32 | 33 | console.log(main('./cities.csv')); 34 | -------------------------------------------------------------------------------- /JavaScript/6-functional-pure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('node:fs'); 4 | 5 | /* fp */ 6 | const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x); 7 | const curry = (fn) => (...args) => fn.bind(null, ...args); 8 | const map = curry((fn, arr) => arr.map(fn)); 9 | const join = curry((str, arr) => arr.join(str)); 10 | const split = curry((splitOn, str) => str.split(splitOn)); 11 | const sort = curry((compareFn, arr) => arr.sort(compareFn)); 12 | const filter = curry((filterFn, arr) => arr.filter(filterFn)); 13 | 14 | /* utility */ 15 | const first = (arr) => arr[0]; 16 | const skipFirst = (arr) => arr.slice(1); 17 | const hasValue = (val) => !!val; 18 | const toStr = (val) => val.toString(); 19 | const appendCell = (row, value) => row.concat(value); 20 | const cellPad = (index, str, width) => ( 21 | index ? str.padStart(width) : str.padEnd(width) 22 | ); 23 | const cellWidth = (index) => [18, 10, 8, 8, 18, 6][index]; 24 | 25 | const renderCell = (cell, index) => ( 26 | cellPad(index, toStr(cell), cellWidth(index)) 27 | ); 28 | const renderRow = pipe(map(renderCell), join('')); 29 | const renderTable = pipe(map(renderRow), join('\n')); 30 | 31 | const getDensityCell = (row) => parseInt(row[3], 10); 32 | const proportion = (max, val) => Math.round(parseInt(val, 10) * 100 / max); 33 | const sortRowsByDensity = sort( 34 | (row1, row2) => getDensityCell(row2) - getDensityCell(row1) 35 | ); 36 | 37 | const calcMaxDensity = pipe( 38 | sortRowsByDensity, 39 | first, 40 | getDensityCell 41 | ); 42 | 43 | const calcRowsProportionToMax = (rows) => (max) => rows.map(pipe( 44 | getDensityCell, 45 | (densityCell) => proportion(max, densityCell) 46 | )); 47 | 48 | const appendProportionCell = (rows) => (proportions) => rows.map( 49 | (row, index) => appendCell(row, proportions[index]) 50 | ); 51 | 52 | const appendTableProportionCol = (rows) => pipe( 53 | calcMaxDensity, 54 | calcRowsProportionToMax(rows), 55 | appendProportionCell(rows) 56 | )(rows); 57 | 58 | const parseTableLines = map(split(',')); 59 | 60 | const toLines = pipe( 61 | split('\n'), 62 | skipFirst, 63 | filter(hasValue) 64 | ); 65 | 66 | const readFile = (file) => fs.readFileSync(file, 'utf8'); 67 | 68 | const getDataset = pipe( 69 | readFile, 70 | toLines, 71 | parseTableLines 72 | ); 73 | 74 | const main = pipe( 75 | getDataset, 76 | appendTableProportionCol, 77 | renderTable 78 | ); 79 | 80 | console.log(main('./cities.csv')); 81 | -------------------------------------------------------------------------------- /JavaScript/7-mixed.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('node:fs'); 4 | 5 | const compose = (...funcs) => (x) => funcs.reduce((x, fn) => fn(x), x); 6 | 7 | const DENSITY_COL = 3; 8 | 9 | const renderTable = (table) => { 10 | const cellWidth = [18, 10, 8, 8, 18, 6]; 11 | return table.map((row) => ( 12 | row.map((cell, i) => { 13 | const width = cellWidth[i]; 14 | return i ? cell.toString().padStart(width) : cell.padEnd(width); 15 | }).join('') 16 | )).join('\n'); 17 | }; 18 | 19 | const proportion = (max, val) => Math.round(val * 100 / max); 20 | 21 | const calcProportion = (table) => { 22 | table.sort((row1, row2) => row2[DENSITY_COL] - row1[DENSITY_COL]); 23 | const maxDensity = table[0][DENSITY_COL]; 24 | table.forEach((row) => { 25 | row.push(proportion(maxDensity, row[DENSITY_COL])); 26 | }); 27 | return table; 28 | }; 29 | 30 | const getDataset = (file) => { 31 | const lines = fs.readFileSync(file, 'utf8').split('\n'); 32 | lines.shift(); 33 | lines.pop(); 34 | return lines.map((line) => line.split(',')); 35 | }; 36 | 37 | const main = compose(getDataset, calcProportion, renderTable); 38 | 39 | console.log(main('./cities.csv')); 40 | -------------------------------------------------------------------------------- /JavaScript/8-declarative.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // 1. Descriptive syntactic structures 4 | // 2. Domain specific language 5 | // 3. No direct computer execution 6 | // 4. No loops, no recursion, no iteration 7 | // 5. Special translator/interpreter needed 8 | // 6. No variables, no side effects 9 | // 7. Task, result or process description 10 | 11 | const task = { 12 | file: './cities.csv', 13 | types: [String, Number, Number, Number, String, Number], 14 | width: [18, 10, 8, 8, 18, 6], 15 | pad: ' ', 16 | separator: ',', 17 | compare: 'density', 18 | order: 'density', 19 | }; 20 | 21 | console.log(task); 22 | -------------------------------------------------------------------------------- /JavaScript/cities.csv: -------------------------------------------------------------------------------- 1 | city,population,area,density,country 2 | Shanghai,24256800,6340,3826,China 3 | Beijing,21516000,16411,1311,China 4 | Delhi,16787941,1484,11313,India 5 | Lagos,16060303,1171,13712,Nigeria 6 | Tianjin,15200000,11760,1293,China 7 | Karachi,14910352,3527,4572,Pakistan 8 | Istanbul,14160467,5461,2593,Turkey 9 | Tokyo,13513734,2191,6168,Japan 10 | Guangzhou,13080500,7434,1759,China 11 | Mumbai,12442373,6034,20680,India 12 | Moscow,12380664,2510,4859,Russia 13 | Sao Paulo,12038175,1521,7914,Brazil 14 | Lahore,11126285,1772,5673,Pakistan 15 | Shenzhen,10467400,1992,5256,China 16 | Jakarta,10075310,664,15171,Indonesia 17 | Seoul,9995784,605,17134,South Korea 18 | Wuhan,9785392,1328,5187,China 19 | Kinshasa,9735000,1118,8710,Congo 20 | Cairo,9278441,3085,3008,Egypt 21 | Dhaka,8906039,300,29687,Bangladesh 22 | Mexico City,8874724,1486,5974,Mexico 23 | Lima,8693387,2672,3253,Peru 24 | London,8673713,1572,5431,United Kingdom 25 | New York City,8537673,784,10892,United States 26 | Bengaluru,8443675,741,11876,India 27 | Bangkok,8280925,1569,5279,Thailand 28 | Ho Chi Minh City,8224400,2096,3925,Vietnam 29 | Dongguan,8220207,2469,3329,China 30 | Chongqing,8189800,5473,1496,China 31 | Nanjing,8187828,4714,1737,China 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2022 How.Programming.Works contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Abstractions 2 | 3 | Programming with any paradigm is an abstractions so here we will collect 4 | code examples for different paradigms to illustrate good and bad practices 5 | in building abstractions. 6 | 7 | [![Слои, связанность и связность кода](https://img.youtube.com/vi/A3RpwNlVeyY/0.jpg)](https://www.youtube.com/watch?v=A3RpwNlVeyY) 8 | --------------------------------------------------------------------------------