├── 04.recursion.js ├── 05.memoization.js ├── 06.lazy_evaluation.js ├── 01.purity.js ├── 07.immutability.js ├── 02.composition.js ├── 03.partial_and_currying.js └── 08.recursive_data_structures.js /04.recursion.js: -------------------------------------------------------------------------------- 1 | function reduceRec(fn, initValue, [x, ...xs]) { 2 | const nextValue = fn(initValue, x); 3 | if (xs.length !== 0) { 4 | return reduceRec(fn, nextValue, xs); 5 | } else { 6 | return nextValue; 7 | } 8 | } 9 | 10 | reduceRec((sum, n) => sum + n, 0, [1, 2, 3]); // 6 11 | -------------------------------------------------------------------------------- /05.memoization.js: -------------------------------------------------------------------------------- 1 | /* == Memoization == */ 2 | 3 | /* If something was computed once, 4 | there's no point in doing this exact 5 | computation again 6 | */ 7 | 8 | function expensiveComputation(n) { 9 | const start = performance.now(); 10 | let result = 0; 11 | while (n-- > 0) { 12 | result += n / Math.random() * n; 13 | } 14 | console.log(`${Math.round(performance.now() - start)}ms`); 15 | return result; 16 | } 17 | 18 | expensiveComputation(100000000); 19 | 20 | function memoize(fn) { 21 | const cache = {}; 22 | return n => { 23 | const result = cache[n]; 24 | if (result !== undefined) { 25 | return result; 26 | } else { 27 | const result = fn(n); 28 | cache[n] = result; 29 | return result; 30 | } 31 | }; 32 | } 33 | 34 | const memoizedExpensiveComputation = memoize(expensiveComputation); 35 | -------------------------------------------------------------------------------- /06.lazy_evaluation.js: -------------------------------------------------------------------------------- 1 | /* == Lazy Evaluation == */ 2 | 3 | const add = (a, b) => a + b; 4 | 5 | add(1, 2); // 3 6 | 7 | class LazyFn { 8 | constructor(fn, args) { 9 | this._fn = fn; 10 | this._args = args; 11 | } 12 | eval() { 13 | const args = this._args.map( 14 | arg => (arg instanceof LazyFn ? arg.eval() : arg) 15 | ); 16 | return this._fn(...args); 17 | } 18 | } 19 | 20 | function intoLazy(fn) { 21 | return (...args) => { 22 | return new LazyFn(add, args); 23 | }; 24 | } 25 | 26 | const lazyAdd = intoLazy(add); 27 | 28 | const thunk = lazyAdd(1, 2); // LazyFn 29 | 30 | thunk.eval(); // 3 31 | 32 | const thunk1 = lazyAdd(1, lazyAdd(2, 3)); 33 | 34 | thunk1.eval(); // 6 35 | 36 | /* sample */ 37 | const list = LazyList(1, 2, 3, 4, 5, 6) 38 | .lazyMap(n => n * n) 39 | .lazyFilter(n => n % 2 === 0); 40 | 41 | list.eval(); // List<[4, 16, 36]> 42 | -------------------------------------------------------------------------------- /01.purity.js: -------------------------------------------------------------------------------- 1 | /* == Pure vs Impure == */ 2 | 3 | /* == Problems with impure code == 4 | 1. Implicit dependencies makes it harder to track values 5 | 2. Implicit dependencies makes it harder to test functions (requires mocks) 6 | 3. Implicit dependencies requires to always think about them (maybe they are mutable?) 7 | */ 8 | 9 | const DB = { user: { id: 132 } }; 10 | 11 | // Impure 12 | 13 | function addProductIntoUserBasket(productId) { 14 | request({ 15 | url: "/api/basket", 16 | method: "POST", 17 | body: { 18 | userId: DB.user.id, 19 | productId 20 | } 21 | }); 22 | } 23 | 24 | getUserById(12); 25 | 26 | // Pure 27 | 28 | function addProductIntoUserBasketPure(userId, productId) { 29 | request({ 30 | url: "/api/basket", 31 | method: "POST", 32 | body: { 33 | productId, 34 | userId 35 | } 36 | }); 37 | } 38 | 39 | getUserByIdPure(DB.user.id, 12); 40 | -------------------------------------------------------------------------------- /07.immutability.js: -------------------------------------------------------------------------------- 1 | /* == JavaScript's primitive value types are immutable == */ 2 | 3 | // Number is immutable 4 | const x = 1; 5 | 6 | x + 1; // 2 7 | 8 | x; // 1 9 | 10 | // String is immutable 11 | const s = "John"; 12 | 13 | s + "Doe"; // "John Doe" 14 | 15 | s; // "John" 16 | 17 | /* == Doing immutable updates in JS == */ 18 | 19 | // Array 20 | const arr = [1, 2]; 21 | 22 | [...arr, 3]; // [1, 2, 3] 23 | [0, ...arr]; // [0, 1, 2] 24 | 25 | const arr1 = [...arr]; 26 | arr1[0] = 10; 27 | arr1; // [10, 2] 28 | 29 | // Object 30 | const obj = { a: 1 }; 31 | 32 | Object.assign({}, obj, { b: 2 }); // { a: 1, b: 2 } 33 | 34 | // Immutable.js, better API 35 | // https://facebook.github.io/immutable-js/ 36 | 37 | const m = Immutable.Map({ a: { b: { c: 1 } } }); 38 | 39 | m1.updateIn(["a", "b", "c"], n => n + 1); // Immutable.Map({ a: { b: { c: 2 } } }) 40 | 41 | /* == Optimizations in immutable data structures 42 | 1. Lazy evaluation 43 | 2. Memoization 44 | 3. Objects pooling 45 | */ 46 | 47 | // Learning materials: https://www.cs.cmu.edu/~rwh/theses/okasaki.pdf 48 | -------------------------------------------------------------------------------- /02.composition.js: -------------------------------------------------------------------------------- 1 | /* == Composition == */ 2 | 3 | /* Functional programming style is all about composition */ 4 | 5 | // A ____________________ > B 6 | 7 | // A ____ > A1 ___ > A1.1 _____ > A2 ____ B 8 | 9 | // Simple event handler 10 | function handleChange1(event) { 11 | const value = event.target.value; 12 | console.log(value); 13 | } 14 | 15 | // Event handler with value processing logic 16 | function handleChange2(event) { 17 | event.preventDefault(); 18 | let value = event.target.value; 19 | value = parseInt(value); 20 | console.log(value); 21 | } 22 | 23 | // Composition 24 | 25 | const targetValue = event => event.target.value; 26 | 27 | const withPreventDefault = event => { 28 | event.preventDefault(); 29 | return event; 30 | }; 31 | 32 | const handleChange3 = event => { 33 | console.log(parseInt(targetValue(withPreventDefault(event)))); 34 | }; 35 | 36 | const handleChange4 = compose( 37 | console.log, 38 | parseInt, 39 | targetValue, 40 | withPreventDefault 41 | ); 42 | 43 | // `compose` implementation 44 | function compose(...fns) { 45 | const [f, ...fs] = fns.reverse(); 46 | return (...args) => fs.reduce((ret, f) => f(ret), f(...args)); 47 | } 48 | -------------------------------------------------------------------------------- /03.partial_and_currying.js: -------------------------------------------------------------------------------- 1 | // == Partial application == 2 | function request(url, opts, onSuccess, onFail) { 3 | //... 4 | } 5 | 6 | const createUser = partial(request, "/api/users", { method: "POST" }); 7 | 8 | createUser(console.log, console.error); 9 | 10 | createUser(saveToDB, logError); 11 | 12 | // `partial` implementation 13 | function partial(fn, ...args) { 14 | return (...restArgs) => fn(...args, ...restArgs); 15 | } 16 | 17 | // partial application in plain JavaScript 18 | const createUser = request.bind(null, "/api/users", { method: "POST" }); 19 | 20 | // == Currying == 21 | 22 | // simple example 23 | const map = curry((fn, coll) => coll.map(fn)); 24 | const mapUserName = map(user => user.name); 25 | 26 | mapUserName([{ name: "John" }, { name: "Mark" }]); // ["John", "Mark"] 27 | 28 | // currying implementation 29 | function curry(fn) { 30 | const ln = fn.length; 31 | return function _curry(...args) { 32 | if (args.length === ln) { 33 | return fn(...args); 34 | } else { 35 | return (...nextArgs) => _curry(...args, ...nextArgs); 36 | } 37 | }; 38 | } 39 | 40 | /* == Principles of paramaters position for better composability == 41 | first comes what's known, 42 | then what's unknown (or changing more frequently than other values) 43 | */ 44 | 45 | const filter = curry((fn, coll) => coll.filter(fn)); 46 | 47 | const filterOdd = filter(n => n % 2 === 0); 48 | 49 | filterOdd([1, 2, 3, 4, 5]); 50 | 51 | filterOdd([6, 3, 9, 1, 5]); 52 | -------------------------------------------------------------------------------- /08.recursive_data_structures.js: -------------------------------------------------------------------------------- 1 | /* == List == 2 | 3 | 0 -> 1 -> 2 -> 3 4 | 5 | */ 6 | 7 | /* == Tree == 8 | 9 | 0 <- Node 10 | / \ 11 | 1 3 <- Node 12 | / / \ 13 | 2 4 5 14 | 15 | */ 16 | 17 | /* == Immutable Singly Linked List == */ 18 | 19 | // `Nil` stands for "nothing" and also "empty list" 20 | class Nil { 21 | toString() { 22 | return "Nil"; 23 | } 24 | } 25 | const nil = new Nil(); 26 | 27 | // `Cons` cell stands for "construction cell" 28 | class Cons { 29 | constructor(head, tail = nil) { 30 | this.head = head; 31 | this.tail = tail; 32 | } 33 | toString() { 34 | return `Cons(${this.head}, ${this.tail})`; 35 | } 36 | } 37 | Cons.of = (head, tail) => new Cons(head, tail); 38 | 39 | const cons = Cons.of(1, Cons.of(2, Cons.of(3))); 40 | 41 | cons.toString(); // "Cons(1, Cons(2, Cons(3, Nil)))" 42 | 43 | class List extends Cons { 44 | constructor(head, tail) { 45 | super(head, tail); 46 | } 47 | toString() { 48 | return `List(${this.first()}, ${this.rest()})`; 49 | } 50 | first() { 51 | return this.head; 52 | } 53 | rest() { 54 | return this.tail; 55 | } 56 | } 57 | List.of = (head, tail) => new List(head, tail); 58 | 59 | List.fromValues = (head, ...tail) => { 60 | if (tail.length > 0) { 61 | return List.of(head, List.fromValues(...tail)); 62 | } else { 63 | return List.of(head, nil); 64 | } 65 | }; 66 | 67 | const list = List.fromValues(1, 2, 3); 68 | 69 | list.toString(); // List(1, List(2, List(3, Nil))) 70 | --------------------------------------------------------------------------------