├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── .gitignore ├── Exercises ├── 2-closure.js ├── 1-callback.js ├── 3-wrapper.js ├── 1-callback.test ├── 2-closure.test └── 3-wrapper.test ├── Solutions ├── 2-closure.js ├── 1-callback.js └── 3-wrapper.js ├── eslint.config.js ├── prettier.config.js ├── README.md ├── .editorconfig ├── JavaScript ├── 3-closure.js ├── 2-callback.js ├── 2-callback-void.js ├── 4-chain.js ├── 1-math.js ├── 6-wrapper.js └── 5-cache.js ├── package.json ├── LICENSE ├── Exercises.en.md └── Exercises.ru.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: tshemsedinov 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /Exercises/2-closure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const store = (x) => null; 4 | 5 | module.exports = { store }; 6 | -------------------------------------------------------------------------------- /Solutions/2-closure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const store = (x) => () => x; 4 | 5 | module.exports = { store }; 6 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const init = require('eslint-config-metarhia'); 4 | 5 | module.exports = init; 6 | -------------------------------------------------------------------------------- /Exercises/1-callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const iterate = (obj, callback) => null; 4 | 5 | module.exports = { iterate }; 6 | -------------------------------------------------------------------------------- /Exercises/3-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const contract = (fn, ...types) => null; 4 | 5 | module.exports = { contract }; 6 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | printWidth: 80, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | tabWidth: 2, 8 | useTabs: false, 9 | semi: true, 10 | }; 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Higher-order functions having functions as arguments or result 2 | 3 | [![Функции высшего порядка, колбеки, события](https://img.youtube.com/vi/1vqATwbGHnc/0.jpg)](https://www.youtube.com/watch?v=1vqATwbGHnc) 4 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /Solutions/1-callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const iterate = (obj, callback) => { 4 | const keys = Object.keys(obj); 5 | for (const key of keys) { 6 | const value = obj[key]; 7 | callback(key, value, obj); 8 | } 9 | }; 10 | 11 | module.exports = { iterate }; 12 | -------------------------------------------------------------------------------- /JavaScript/3-closure.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fn = (a) => { 4 | const b = 'Closure variable'; 5 | return (c) => { 6 | console.dir({ a, b, c }); 7 | }; 8 | }; 9 | 10 | const f1 = fn('Parameter 1'); 11 | f1('Parameter 2'); 12 | 13 | const f2 = fn('Parameter X'); 14 | f2('Parameter Y'); 15 | -------------------------------------------------------------------------------- /JavaScript/2-callback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fn = (par, callback) => { 4 | if (!par) { 5 | callback(new Error('Parameter needed')); 6 | return 'None'; 7 | } 8 | callback(null, `Data ${par}`); 9 | return 'Value'; 10 | }; 11 | 12 | const res = fn('example', (err, data) => { 13 | if (err) throw err; 14 | console.dir({ data }); 15 | }); 16 | 17 | console.dir({ res }); 18 | -------------------------------------------------------------------------------- /JavaScript/2-callback-void.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fn = (par, callback) => { 4 | if (!par) { 5 | return void callback(new Error('Parameter needed')); 6 | } 7 | return void callback(null, `Data ${par}`); 8 | }; 9 | 10 | const res = fn('example', (err, data) => { 11 | if (err) throw err; 12 | console.dir({ data }); 13 | return 'Ignored result'; 14 | }); 15 | 16 | console.dir({ res }); 17 | -------------------------------------------------------------------------------- /JavaScript/4-chain.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function fn(a) { 4 | return function (b) { 5 | return function (c) { 6 | return a + b + c; 7 | }; 8 | }; 9 | } 10 | 11 | // const fn = a => b => c => a + b + c; 12 | 13 | const f1 = fn(1); 14 | const f2 = f1(2); 15 | const res1 = f2(3); 16 | const res2 = fn(1)(2)(3); 17 | 18 | if (res1 === res2) { 19 | console.log('a + b + c =', res1); 20 | } 21 | -------------------------------------------------------------------------------- /JavaScript/1-math.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { sin } = Math; 4 | const π = Math.PI; 5 | 6 | const inverse = (f) => (x) => 1 / f(x); 7 | 8 | const cosecant = inverse(sin); 9 | 10 | const a = cosecant(0.25); 11 | console.log('cosecant(0.25) =', a); 12 | 13 | const sin2 = inverse(cosecant); 14 | 15 | console.log('sin(0) =', sin(0), '=', sin2(0)); 16 | console.log('sin(π / 2) =', sin(π / 2), '=', sin2(π / 2)); 17 | console.log('sin(π / 4) =', sin(π / 4), '=', sin2(π / 4)); 18 | -------------------------------------------------------------------------------- /JavaScript/6-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const logable = (fn) => (...args) => { 4 | const res = fn(...args); 5 | const callArgs = args.join(', '); 6 | const callLog = `${fn.name}(${callArgs})`; 7 | console.log(`Call: ${callLog} Result: ${res}`); 8 | return res; 9 | }; 10 | 11 | // Usage 12 | 13 | const sum = (a, b) => a + b; 14 | 15 | const logableSum = logable(sum); 16 | const a = logableSum(3, 5); 17 | const b = logableSum(7, 2); 18 | const c = logableSum(1, -1); 19 | console.dir({ a, b, c }); 20 | -------------------------------------------------------------------------------- /JavaScript/5-cache.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fn = () => { 4 | console.log('Generate cache'); 5 | const cache = {}; 6 | return (key) => { 7 | let res = cache[key]; 8 | if (res) { 9 | console.log('From cache'); 10 | return res; 11 | } 12 | console.log('Calculate and save to cache'); 13 | res = 'value' + key; 14 | cache[key] = res; 15 | return res; 16 | }; 17 | }; 18 | 19 | const f1 = fn(); 20 | const f2 = fn(); 21 | 22 | f1(1); 23 | f1(2); 24 | f1(1); 25 | f1(2); 26 | 27 | f2(1); 28 | f2(2); 29 | f2(1); 30 | f2(2); 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Check labs 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Node.js 11 | uses: actions/setup-node@v1 12 | with: 13 | node-version: 14 14 | - uses: actions/cache@v2 15 | with: 16 | path: ~/.npm 17 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 18 | restore-keys: | 19 | ${{ runner.os }}-node- 20 | - run: npm ci 21 | - run: npm t 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "private": true, 4 | "version": "1.0.5", 5 | "author": "Timur Shemsedinov ", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "eslint ./Exercises; hpw", 9 | "ci": "eslint ./Exercises && hpw", 10 | "lint": "eslint . && prettier -c \"**/*.js\"", 11 | "fix": "eslint . --fix && prettier --write \"**/*.js\"" 12 | }, 13 | "dependencies": { 14 | "hpw": "^0.2.4", 15 | "eslint": "^9.12.0", 16 | "eslint-config-metarhia": "^9.1.1", 17 | "prettier": "^3.3.3" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Solutions/3-wrapper.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const contract = (fn, ...types) => (...args) => { 4 | for (let i = 0; i < args.length; i++) { 5 | const arg = args[i]; 6 | const def = types[i]; 7 | const name = def.name.toLowerCase(); 8 | if (typeof arg !== name) { 9 | throw new TypeError(`Argument type ${name} expected`); 10 | } 11 | } 12 | const res = fn(...args); 13 | const def = types[types.length - 1]; 14 | const name = def.name.toLowerCase(); 15 | if (typeof res !== name) { 16 | throw new TypeError(`Result type ${name} expected`); 17 | } 18 | return res; 19 | }; 20 | 21 | module.exports = { contract }; 22 | -------------------------------------------------------------------------------- /Exercises/1-callback.test: -------------------------------------------------------------------------------- 1 | ({ 2 | name: 'iterate', 3 | length: [120, 180], 4 | test: (iterate) => { 5 | { 6 | const obj = { a: 1, b: 2, c: 3 }; 7 | const obj2 = {}; 8 | iterate(obj, (key, value, object) => { 9 | obj2[key] = value; 10 | if (object !== obj) { 11 | const msg = `Expected iterating object as a 3rd argument of callback`; 12 | throw new Error(msg); 13 | } 14 | }); 15 | const initial = JSON.stringify(obj); 16 | const cloned = JSON.stringify(obj2); 17 | if (initial !== cloned) { 18 | const msg = `Result ${cloned} instead of expected ${initial}`; 19 | throw new Error(msg); 20 | } 21 | } 22 | } 23 | }) 24 | -------------------------------------------------------------------------------- /Exercises/2-closure.test: -------------------------------------------------------------------------------- 1 | ({ 2 | name: 'store', 3 | length: [10, 15], 4 | test: (store) => { 5 | { 6 | const expected = 5; 7 | const read = store(expected); 8 | if (typeof read !== 'function') { 9 | const msg = `Function store() result expected to be function`; 10 | throw new Error(msg); 11 | } 12 | const value = read(); 13 | if (typeof value !== 'number') { 14 | const msg = `Result store(${expected})() expected to be number`; 15 | throw new Error(msg); 16 | } 17 | if (value !== expected) { 18 | const msg = `Result ${value} instead of expected ${expected}`; 19 | throw new Error(msg); 20 | } 21 | } 22 | { 23 | const expected = 7; 24 | const read = store(expected); 25 | const value = read(); 26 | if (value !== expected) { 27 | const msg = `Result ${value} instead of expected ${expected}`; 28 | throw new Error(msg); 29 | } 30 | } 31 | } 32 | }) 33 | -------------------------------------------------------------------------------- /Exercises/3-wrapper.test: -------------------------------------------------------------------------------- 1 | ({ 2 | name: 'contract', 3 | length: [300, 600], 4 | test: (contract) => { 5 | { 6 | const f = (a, b) => a + b; 7 | const g = contract(f, Number, Number, Number); 8 | const res = g(2, 3); 9 | if (res !== 5) { 10 | throw new Error(`Result expected: 5:Number`); 11 | } 12 | } 13 | { 14 | const f = (a, b) => a + b + ''; 15 | const g = contract(f, Number, Number, Number); 16 | let error; 17 | try { 18 | const res = g(2, 3); 19 | } catch (err) { 20 | error = err; 21 | } 22 | if (!error) { 23 | throw new Error(`TypeError expected, checking result`); 24 | } 25 | } 26 | { 27 | const f = (a, b) => a + b; 28 | const g = contract(f, Number, Number, Number); 29 | let error; 30 | try { 31 | const res = g(2, '3'); 32 | } catch (err) { 33 | error = err; 34 | } 35 | if (!error) { 36 | throw new Error(`TypeError expected, checking arguments`); 37 | } 38 | } 39 | } 40 | }) 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2024 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 | -------------------------------------------------------------------------------- /Exercises.en.md: -------------------------------------------------------------------------------- 1 | ## Higher-order functions 2 | 3 | 1. Implement function `iterate(object, callback)` to iterate given object 4 | passing each element to callback with the following contract 5 | `callback(key, value, object)`. Example: 6 | 7 | ```js 8 | const obj = { a: 1, b: 2, c: 3 }; 9 | iterate(obj, (key, value) => { 10 | console.log({ key, value }); 11 | }); 12 | ``` 13 | Output: 14 | ``` 15 | { key: 'a', value: 1 } 16 | { key: 'b', value: 2 } 17 | { key: 'c', value: 3 } 18 | ``` 19 | 20 | 2. Implement function `store(value)` to store `value` inside closure of 21 | returning function. After calling returning function it will return a value 22 | from closure, like in the following example: 23 | 24 | ```js 25 | const read = store(5); 26 | const value = read(); 27 | console.log(value); // Output: 5 28 | ``` 29 | 30 | 3. Implement function `contract(fn, ...types)` to wrap `fn` (first argument) and 31 | check argument types (all arguments except first and last) and result type (last 32 | argument). Generates `TypeError` exception if wrong types detected. As in 33 | following example: 34 | 35 | ```js 36 | const add = (a, b) => a + b; 37 | const addNumbers = contract(add, Number, Number, Number); 38 | const res = addNumbers(2, 3); 39 | console.dir(res); // Output: 5 40 | ``` 41 | and 42 | ```js 43 | const concat = (s1, s2) => s1 + s2; 44 | const concatStrings = contract(concat, String, String, String); 45 | const res = concatStrings('Hello ', 'world!'); 46 | console.dir(res); // Output: Hello world! 47 | ``` 48 | -------------------------------------------------------------------------------- /Exercises.ru.md: -------------------------------------------------------------------------------- 1 | ## Функции высшего порядка 2 | 3 | 1. Реализуйте функцию `iterate(object, callback)` которая будет итерировать 4 | все ключи переданного объекта, вызывая для каждого `callback` со следующим 5 | контрактом `callback(key, value, object)`. Например: 6 | 7 | ```js 8 | const obj = { a: 1, b: 2, c: 3 }; 9 | iterate(obj, (key, value) => { 10 | console.log({ key, value }); 11 | }); 12 | ``` 13 | Вывод: 14 | ``` 15 | { key: 'a', value: 1 } 16 | { key: 'b', value: 2 } 17 | { key: 'c', value: 3 } 18 | ``` 19 | 20 | 2. Реализуйте функцию `store(value)` которая сохранит значение в замыкании 21 | возвращаемой функции, а после ее вызова вернет значение из замыкания, как 22 | в примере: 23 | 24 | ```js 25 | const read = store(5); 26 | const value = read(); 27 | console.log(value); // Output: 5 28 | ``` 29 | 30 | 3. Реализуйте функцию `contract(fn, ...types)` которая оборачивает `fn` (первый 31 | аргумент) и проверяет типы аргументов (все аргументы кроме первого и последнего) 32 | и результата (последний аргумент), генерируя исключение `TypeError`, если типы 33 | не совпадают. Как в следующем примере: 34 | 35 | ```js 36 | const add = (a, b) => a + b; 37 | const addNumbers = contract(add, Number, Number, Number); 38 | const res = addNumbers(2, 3); 39 | console.dir(res); // Output: 5 40 | ``` 41 | and 42 | ```js 43 | const concat = (s1, s2) => s1 + s2; 44 | const concatStrings = contract(concat, String, String, String); 45 | const res = concatStrings('Hello ', 'world!'); 46 | console.dir(res); // Output: Hello world! 47 | ``` 48 | --------------------------------------------------------------------------------