├── 10 ├── 01-not-the-same-as-lists.js ├── 02-lists-to-sets-and-back.js ├── 03-lazy-duplicate-removal.js ├── 04-sorting-ordered-sets.js ├── 05-iterating-ordered-sets.js └── 06-adding-duplicates-to-sets.js ├── 11 ├── 01-set-intersections.js ├── 02-list-intersections.js ├── 03-set-differences.js ├── 04-list-differences.js ├── 05-map-key-differences.js └── 06-list-subsets.js ├── 12 ├── 01-merging-maps-by-key.js ├── 02-merging-maps-with-complex-keys.js ├── 03-merging-lists-of-simple-values.js ├── 04-merging-lists-of-maps.js ├── 05-merging-lists-of-lists.js ├── 06-concatenating-list-values.js ├── 07-lazy-concatenating-sequences.js ├── 08-interposing-values.js └── 09-interleaving-values.js ├── 13 ├── 01-maps-of-behavior.js ├── 02-wrapping-behavior-maps-in-functions.js ├── 03-providing-default-behavior.js ├── 04-parameterizing-behavior.js ├── 05-composing-behavior.js └── 06-declarative-and-or-conditions.js ├── 14 ├── immutable-dom.html ├── immutable-dom.js ├── immutable-react.html ├── immutable-react.js ├── immutable-react.jsx └── styles.css ├── 15 ├── 01-reading-lines-of-data-into-collections.js ├── 02-reading-words-concat.js ├── 03-reading-words-push.js ├── 04-reading-words-with-mutations.js ├── 05-writing-collection-data.js ├── 06-writing-sequence-data.js ├── 07-chaining-lazy-io-streams.js ├── input │ ├── 01-data.csv │ └── words └── output │ ├── 05-writing-collection-data │ └── 06-writing-sequence-data ├── 16 ├── immutable-app.js ├── immutable-dom.html ├── immutable-dom.js └── styles.css ├── .babelrc ├── .eslintrc ├── .gitignore ├── 02 ├── 01-available-types.js ├── 02-passing-javascript-data.js ├── 03-passing-immutable-data.js ├── 04-the-of-method.js └── 05-the-fromjs-function.js ├── 03 ├── 01-new-list-items.js ├── 02-new-map-key-value-pairs.js ├── 03-pushing-multiple-list-values.js ├── 04-setting-multiple-map-values.js ├── 05-changing-list-items.js ├── 06-updating-list-values.js ├── 07-changing-map-values.js ├── 08-updating-map-values.js ├── 09-chaining-collection-mutation-methods.js ├── 10-deleting-list-items.js ├── 11-deleting-map-key-value-pairs.js ├── 12-chaining-removal-methods.js ├── 13-deleting-by-replacing-collections.js ├── 14-deleting-by-clearing-collections.js └── 15-mutation-history-stacks.js ├── 04 ├── 01-filtering-by-value.js ├── 02-filtering-by-greater-lesser.js ├── 03-filtering-by-negation.js ├── 04-filtering-maps-by-keys.js ├── 05-filtering-by-fancy-keys.js ├── 06-existence-checks.js ├── 07-finding-items.js ├── 08-the-is-function.js ├── 09-searching-lists-of-maps.js ├── 10-partial-map-search-matches.js └── 11-search-direction.js ├── 05 ├── 01-basic-sequence-creation.js ├── 02-collections-to-sequences.js ├── 03-for-loop-iteration.js ├── 04-the-for-each-iterator.js ├── 05-sequence-filters.js ├── 06-multiple-sequence-filters.js ├── 07-removing-work-with-take.js └── 08-pagination-using-slice.js ├── 06 ├── 01-deafult-sorting.js ├── 02-reversed-sorting.js ├── 03-sorting-lists-of-maps.js ├── 04-ordering-maps.js ├── 05-sorting-maps.js └── 06-maintaining-sort-order.js ├── 07 ├── 01-plucking-property-values.js ├── 02-computing-new-values.js ├── 03-mapping-new-properties.js ├── 04-mapping-specific-properties.js ├── 05-reducing-max-min.js ├── 06-accumulating-values.js ├── 07-multiple-map-calls.js ├── 08-filtering-before-mapping.js └── 09-filter-map-reduce.js ├── 08 ├── 01-zipping-lists-of-simple-values.js ├── 02-zipping-lists-of-maps.js ├── 03-lazy-zipping.js ├── 04-deep-flattening-and-filtering.js ├── 05-shallow-flattening.js └── 06-flattening-nested-maps.js ├── 09 ├── 01-strict-equality.js ├── 02-strict-equality-vs-equals.js ├── 03-transformations-versus-mutations.js ├── 04-detecting-after-transfomations.js ├── 05-detecting-before-transfomations.js └── 06-caching-function-arguments.js ├── LICENSE ├── README.md ├── package-lock.json └── package.json /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["transform-es2015-modules-commonjs"] 3 | } 4 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "node": true, 5 | "browser": true 6 | }, 7 | "parserOptions": { 8 | "sourceType": "module", 9 | "ecmaVersion": 8 10 | }, 11 | "rules": { 12 | "comma-dangle": ["error", "never"], 13 | "no-console": 0, 14 | "no-confusing-arrow": 0, 15 | "no-restricted-syntax": 0, 16 | "no-bitwise": 0, 17 | "no-nested-ternary": 0, 18 | "no-await-in-loop": 0 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /02/01-available-types.js: -------------------------------------------------------------------------------- 1 | import { 2 | List, 3 | Map, 4 | OrderedMap, 5 | Set, 6 | OrderedSet, 7 | Seq, 8 | Stack, 9 | Record 10 | } from 'immutable'; 11 | 12 | // Returns new instances of immutable data types. 13 | const myList = List(); 14 | const myMap = Map(); 15 | const myOrderedMap = OrderedMap(); 16 | const mySet = Set(); 17 | const myOrderedSet = OrderedSet(); 18 | const mySeq = Seq(); 19 | const myStack = Stack(); 20 | 21 | // The Record() function returns a new class, 22 | // so we have to instantiate this ourselves. 23 | const MyRecord = Record({}); 24 | const myRecord = new MyRecord(); 25 | 26 | console.log('List', myList instanceof List); 27 | // -> List true 28 | console.log('Map', myMap instanceof Map); 29 | // -> Map true 30 | console.log('OrderedMap', myOrderedMap instanceof OrderedMap); 31 | // -> OrderedMap true 32 | console.log('Set', mySet instanceof Set); 33 | // -> Set true 34 | console.log('OrderedSet', myOrderedSet instanceof OrderedSet); 35 | // -> OrderedSet true 36 | console.log('Seq', mySeq instanceof Seq); 37 | // -> Seq true 38 | console.log('Stack', myStack instanceof Stack); 39 | // -> Stack true 40 | console.log('Record', myRecord instanceof Record); 41 | // -> Record true 42 | -------------------------------------------------------------------------------- /02/02-passing-javascript-data.js: -------------------------------------------------------------------------------- 1 | import { 2 | List, 3 | Map 4 | } from 'immutable'; 5 | 6 | // Lists can take Javascript arrays while Maps can 7 | // take Javascript objects. 8 | const myList = List([1, 2, 3]); 9 | const myMap = Map({ a: 1, b: 2, c: 3 }); 10 | 11 | // Lists and Maps have a get() method. 12 | console.log('myList', myList.get(1)); 13 | // -> myList 2 14 | console.log('myMap', myMap.get('b')); 15 | // -> myMap 2 16 | -------------------------------------------------------------------------------- /02/03-passing-immutable-data.js: -------------------------------------------------------------------------------- 1 | import { 2 | List, 3 | Map 4 | } from 'immutable'; 5 | 6 | // Passing other Immutable.js types works fine. 7 | const myList = List(List([1, 2, 3])); 8 | const firstMap = Map({ a: 1, b: 2, c: 3 }); 9 | const myMap = Map(firstMap); 10 | 11 | // We get List and Map instances as expected. 12 | console.log('myList', myList.get(0)); 13 | // -> myList 1 14 | console.log('myMap', myMap.get('a')); 15 | // -> myMap 1 16 | 17 | // But the same instance is reused. Why not? It can't 18 | // change! 19 | console.log('firstMap === myMap', firstMap === myMap); 20 | // -> firstMap === myMap true 21 | 22 | const myFunc = map => Map(map).toJS(); 23 | 24 | console.log('myFunc(object)', myFunc({ a: 1, b: 2, c: 3 })); 25 | // -> myFunc(object) { a: 1, b: 2, c: 3 } 26 | console.log('myFunc(map)', myFunc(myMap)); 27 | // -> myFunc(map) { a: 1, b: 2, c: 3 } 28 | -------------------------------------------------------------------------------- /02/04-the-of-method.js: -------------------------------------------------------------------------------- 1 | import { 2 | List, 3 | Map, 4 | Set, 5 | Seq 6 | } from 'immutable'; 7 | 8 | // The of() method is like a static class method. 9 | // You don't need to pass it an intermediary array 10 | // or object. Just the arguments used to build the 11 | // collection. 12 | const myList = List.of(1, 2, 3); 13 | const myMap = Map.of( 14 | 'a', 1, 15 | 'b', 2, 16 | 'c', 3 17 | ); 18 | const mySet = Set.of(1, 2, 3); 19 | const mySeq = Seq.of(1, 2, 3); 20 | 21 | // The end result is the same List/Map. 22 | console.log('myList', myList.toJS()); 23 | // -> myList [ 1, 2, 3 ] 24 | console.log('myMap', myMap.toJS()); 25 | // -> myMap { a: 1, b: 2, c: 3 } 26 | console.log('mySet', mySet.toJS()); 27 | // -> mySet [ 1, 2, 3 ] 28 | console.log('mySeq', mySeq.toJS()); 29 | // -> mySeq [ 1, 2, 3 ] 30 | -------------------------------------------------------------------------------- /02/05-the-fromjs-function.js: -------------------------------------------------------------------------------- 1 | // We don't need to import specific immutable 2 | // types. Just the function that knows how to 3 | // deeply parse everything we need. 4 | import { fromJS } from 'immutable'; 5 | 6 | // The fromJS() function knows how to parse simple 7 | // JSON-like data into immutable data types. 8 | const myList = fromJS([1, 2, 3]); 9 | const myMap = fromJS({ 10 | a: { 11 | b: ['c', 'd'], 12 | e: { 13 | f: 'g', 14 | h: 'i' 15 | } 16 | } 17 | }); 18 | 19 | // As usual, we can use get to read list data. 20 | console.log('myList', myList.get(0)); 21 | // -> myList 1 22 | 23 | // We can also read inside of the nested immutable 24 | // map to find what we nned. 25 | console.log( 26 | 'myMap nested list', 27 | myMap.getIn(['a', 'b']).toJS() 28 | ); 29 | // -> myMap nested list [ 'c', 'd' ] 30 | 31 | console.log('myMap nested value', myMap.getIn(['a', 'b', 1])); 32 | // -> myMap nested value d 33 | -------------------------------------------------------------------------------- /03/01-new-list-items.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | // The initial list of numbers. 4 | const myList = List.of(1, 2, 3); 5 | 6 | // Calling push() creates a new List instance. 7 | const myChangedList = myList.push(4); 8 | 9 | // myList is unchanged, myChangedList has the 10 | // new value. 11 | console.log('myList', myList.toJS()); 12 | // -> myList [ 1, 2, 3 ] 13 | console.log('myChangedList', myChangedList.toJS()); 14 | // -> myChangedList [ 1, 2, 3, 4 ] 15 | -------------------------------------------------------------------------------- /03/02-new-map-key-value-pairs.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | // The initial map of numbers. 4 | const myMap = Map.of( 5 | 'one', 1, 6 | 'two', 2, 7 | 'three', 3 8 | ); 9 | 10 | // Calling set() adds a new key-value pair to the 11 | // map by creating a new Map instance. 12 | const myChangedMap = myMap.set('four', 4); 13 | 14 | console.log('myMap', myMap.toJS()); 15 | // -> myMap { one: 1, two: 2, three: 3 } 16 | console.log('myChangedMap', myChangedMap.toJS()); 17 | // -> myChangedMap { one: 1, two: 2, three: 3, four: 4 } 18 | -------------------------------------------------------------------------------- /03/03-pushing-multiple-list-values.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | 5 | // We can chain together calls to push(), creating 6 | // intermediary collections that are garbage-collected 7 | // at the first opportunity. 8 | const myChangedList = myList 9 | .push(4) 10 | .push(5, 6) 11 | .push(7, 8) 12 | .push(9); 13 | 14 | console.log('myList', myList.toJS()); 15 | // -> myList [ 1, 2, 3 ] 16 | console.log('myChangedList', myChangedList); 17 | // -> myChangedList List [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] 18 | -------------------------------------------------------------------------------- /03/04-setting-multiple-map-values.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myMap = Map.of( 4 | 'one', 1, 5 | 'two', 2, 6 | 'three', 3 7 | ); 8 | const myChangedMap = myMap 9 | .set('four', 4) 10 | .set('five', 5) 11 | .set('six', 6); 12 | 13 | console.log('myMap', myMap.toJS()); 14 | // -> myMap { one: 1, two: 2, three: 3 } 15 | console.log('myChangedMap', myChangedMap.toJS()); 16 | // -> myChangedMap { one: 1, two: 2, 17 | // -> three: 3, four: 4, 18 | // -> five: 5, six: 6 } 19 | -------------------------------------------------------------------------------- /03/05-changing-list-items.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | // A single-item list - the number 1. 4 | const myList = List.of(1); 5 | 6 | // Calling set() with the index of the item to change 7 | // and the new value, creates a new list. 8 | const myChangedList = myList.set(0, 2); 9 | 10 | console.log('myList', myList.toJS()); 11 | // -> myList [ 1 ] 12 | console.log('myChangedList', myChangedList.toJS()); 13 | // -> myChangedList [ 2 ] 14 | -------------------------------------------------------------------------------- /03/06-updating-list-values.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of( 4 | Map.of('total', 0, 'step', 5), 5 | Map.of('total', 5, 'step', 10) 6 | ); 7 | 8 | // A function that increments the total key based 9 | // on the step key. 10 | const increment = map => map.set( 11 | 'total', 12 | map.get('total') + map.get('step') 13 | ); 14 | 15 | // By passing the increment() function to update(), 16 | // we can change list items based on their current value. 17 | const myChangedList = myList 18 | .update(0, increment) 19 | .update(1, increment); 20 | 21 | console.log('myList', myList.toJS()); 22 | // -> myList [ { total: 0, step: 5 }, { total: 5, step: 10 } ] 23 | console.log('myChangedList', myChangedList.toJS()); 24 | // -> myChangedList [ { total: 5, step: 5 }, { total: 15, step: 10 } ] 25 | -------------------------------------------------------------------------------- /03/07-changing-map-values.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | // A map with a single key-value pair. 4 | const myMap = Map.of('one', 1); 5 | 6 | // Calling set() to change the value of a given 7 | // key results in a new map. 8 | const myChangedMap = myMap.set('one', 'one'); 9 | 10 | console.log('myMap', myMap.toJS()); 11 | // -> myMap { one: 1 } 12 | console.log('myChangedMap', myChangedMap.toJS()); 13 | // -> myChangedMap { one: 'one' } 14 | -------------------------------------------------------------------------------- /03/08-updating-map-values.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of( 4 | Map.of('total', 0, 'step', 5), 5 | Map.of('total', 5, 'step', 10) 6 | ); 7 | 8 | // A function that increments the total key based 9 | // on the step key. 10 | const increment = map => map.update( 11 | 'total', 12 | t => t + map.get('step') 13 | ); 14 | 15 | // By passing the increment() function to update(), 16 | // we can change list items based on their current value. 17 | const myChangedList = myList 18 | .update(0, increment) 19 | .update(1, increment); 20 | 21 | console.log('myList', myList.toJS()); 22 | // -> myList [ { total: 0, step: 5 }, { total: 5, step: 10 } ] 23 | console.log('myChangedList', myChangedList.toJS()); 24 | // -> myChangedList [ { total: 5, step: 5 }, { total: 15, step: 10 } ] 25 | -------------------------------------------------------------------------------- /03/09-chaining-collection-mutation-methods.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | const myMap = Map.of('one', 1, 'two', 2); 5 | 6 | // We can chain together insertion, and update 7 | // list methods. 8 | const myChangedList = myList 9 | .push(4) 10 | .set(3, 5) 11 | .update(3, n => n + 5); 12 | 13 | // We can chain together insertion, and update 14 | // map methods. 15 | const myChangedMap = myMap 16 | .set('three', 3) 17 | .set('three', 5) 18 | .update('three', t => t + 5); 19 | 20 | console.log('myList', myList.toJS()); 21 | // -> myList [ 1, 2, 3 ] 22 | console.log('myMap', myMap.toJS()); 23 | // -> myMap { one: 1, two: 2 } 24 | console.log('myChangedList', myChangedList.toJS()); 25 | // -> myChangedList [ 1, 2, 3, 10 ] 26 | console.log('myChangedMap', myChangedMap.toJS()); 27 | // -> myChangedMap { one: 1, two: 2, three: 10 } 28 | -------------------------------------------------------------------------------- /03/10-deleting-list-items.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | 5 | // Calling remove() will create a new List with the 6 | // item at the specified index removed. 7 | const myChangedList = myList.remove(0); 8 | 9 | console.log('myList', myList.toJS()); 10 | // -> myList [ 1, 2, 3 ] 11 | console.log('myChangedList', myChangedList.toJS()); 12 | // -> myChangedList [ 2, 3 ] 13 | -------------------------------------------------------------------------------- /03/11-deleting-map-key-value-pairs.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myMap = Map.of( 4 | 'one', 1, 5 | 'two', 2, 6 | 'three', 3 7 | ); 8 | 9 | // Calling delete() will create a new Map 10 | // with the specified key-value removed. 11 | const myChangedMap = myMap.remove('one'); 12 | 13 | console.log('myMap', myMap.toJS()); 14 | // -> myMap { one: 1, two: 2, three: 3 } 15 | console.log('myChangedMap', myChangedMap.toJS()); 16 | // -> myChangedMap { three: 3, two: 2 } 17 | -------------------------------------------------------------------------------- /03/12-chaining-removal-methods.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3, 4); 4 | const myMap = Map.of( 5 | 'one', 1, 'two', 2, 6 | 'three', 3, 'four', 4, 7 | 'five', 5, 'six', 6 8 | ); 9 | 10 | // We're chaining two remove() calls together with the 11 | // same index argument because removing a list value 12 | // results in all values to the right, shifting to the 13 | // left by one index. 14 | const myChangedList = myList 15 | .remove(1) 16 | .remove(1); 17 | 18 | // With maps, we can chain remove() calls with one key 19 | // each, pass several keys to removeAll(), or both. 20 | const myChangedMap = myMap 21 | .remove('six') 22 | .removeAll(['five', 'four', 'three']); 23 | 24 | console.log('myList', myList.toJS()); 25 | // -> myList [ 1, 2, 3, 4 ] 26 | console.log('myMap', myMap.toJS()); 27 | // -> myMap { one: 1, two: 2, three: 3, four: 4, five: 5, six: 6 } 28 | console.log('myChangedList', myChangedList.toJS()); 29 | // -> myChangedList [ 1, 4 ] 30 | console.log('myChangedMap', myChangedMap.toJS()); 31 | // -> myChangedMap { one: 1, two: 2 } 32 | -------------------------------------------------------------------------------- /03/13-deleting-by-replacing-collections.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // Empties the collection by determining the 4 | // collection type, then retuning a new instance. 5 | const empty = (collection) => { 6 | if (collection instanceof List) { 7 | return List(); 8 | } 9 | if (collection instanceof Map) { 10 | return Map(); 11 | } 12 | return null; 13 | }; 14 | 15 | // List and Map instances. 16 | const myList = List.of(1, 2); 17 | const myMap = Map.of('one', 1, 'two', 2); 18 | 19 | // Create new instances by emptying the 20 | // existing instances. 21 | const myEmptyList = empty(myList); 22 | const myEmptyMap = empty(myMap); 23 | 24 | console.log('myList', myList.toJS()); 25 | // -> myList [ 1, 2 ] 26 | console.log('myEmptyList', myEmptyList.toJS()); 27 | // -> myEmptyList [] 28 | console.log('myMap', myMap.toJS()); 29 | // -> myMap { one: 1, two: 2 } 30 | console.log('myEmptyMap', myEmptyMap.toJS()); 31 | // -> myEmptyMap {} 32 | -------------------------------------------------------------------------------- /03/14-deleting-by-clearing-collections.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // List and Map instances. 4 | const myList = List.of(1, 2); 5 | const myMap = Map.of('one', 1, 'two', 2); 6 | 7 | // Create new instances by emptying the 8 | // existing instances. 9 | const myEmptyList = myList.clear(); 10 | const myEmptyMap = myMap.clear(); 11 | 12 | console.log('myList', myList.toJS()); 13 | // -> myList [ 1, 2 ] 14 | console.log('myEmptyList', myEmptyList.toJS()); 15 | // -> myEmptyList [] 16 | console.log('myMap', myMap.toJS()); 17 | // -> myMap { one: 1, two: 2 } 18 | console.log('myEmptyMap', myEmptyMap.toJS()); 19 | // -> myEmptyMap {} 20 | -------------------------------------------------------------------------------- /03/15-mutation-history-stacks.js: -------------------------------------------------------------------------------- 1 | import { List, Stack } from 'immutable'; 2 | 3 | // Names of List and Map methods that perform 4 | // persistent changes. These are the methods that 5 | // we want to build history from. 6 | const persistentChanges = [ 7 | 'set', 8 | 'delete', 9 | 'deleteAll', 10 | 'clear', 11 | 'update', 12 | 'merge', 13 | 'mergeWith', 14 | 'mergeDeep', 15 | 'mergeDeepWith', 16 | 'setIn', 17 | 'deleteIn', 18 | 'updateIn', 19 | 'mergeIn', 20 | 'mergeDeepIn', 21 | 'insert', 22 | 'push', 23 | 'pop', 24 | 'unshift', 25 | 'shift', 26 | 'setSize' 27 | ]; 28 | 29 | // Where the history for a given collection is stored. 30 | const mutations = new WeakMap(); 31 | 32 | // Defines proxy behavior for collection instances. 33 | // It's a way to trap method calls and redirect them 34 | // to instances in the mutations map. 35 | const historyHandler = { 36 | get(target, key) { 37 | // Before we do anything, make sure that the 38 | // target collection has a Stack instance in 39 | // the mutations map. 40 | if (!mutations.has(target)) { 41 | mutations.set( 42 | target, 43 | Stack().unshift(target) 44 | ); 45 | } 46 | 47 | // Get the mutation history for this collection. 48 | const stack = mutations.get(target); 49 | 50 | // Check if the caller is calling one of the 51 | // recognized persistent change methods. 52 | if (persistentChanges.includes(key)) { 53 | // Return a function that calls the method in question 54 | // on the most recent stack item. 55 | return (...args) => { 56 | const result = stack.first()[key](...args); 57 | 58 | // Store the result as the newest item on the stack. 59 | mutations.set( 60 | target, 61 | stack.unshift(result) 62 | ); 63 | 64 | // Return the result like normal. 65 | return result; 66 | }; 67 | 68 | // The caller is calling the undo() method. Remove the 69 | // first item on the stack, if there's more than 70 | // one item. 71 | } else if (key === 'undo') { 72 | return () => 73 | mutations.set( 74 | target, 75 | stack.count() > 1 ? stack.shift() : stack 76 | ); 77 | } 78 | // Not a persistent change method, just call it and 79 | // return the result. 80 | return stack.first()[key]; 81 | } 82 | }; 83 | 84 | // Wraps a List instance with the historyHandler proxy. 85 | const myList = new Proxy(List.of(1, 2, 3), historyHandler); 86 | console.log('myList', myList.toJS()); 87 | // -> myList [ 1, 2, 3 ] 88 | 89 | myList.push(4); 90 | console.log('push(4)', myList.toJS()); 91 | // -> push(4) [ 1, 2, 3, 4 ] 92 | myList.delete(0); 93 | console.log('delete(0)', myList.toJS()); 94 | // -> delete(0) [ 2, 3, 4 ] 95 | myList.undo(); 96 | console.log('undo()', myList.toJS()); 97 | // -> undo() [ 1, 2, 3, 4 ] 98 | myList.undo(); 99 | console.log('undo()', myList.toJS()); 100 | // -> undo() [ 1, 2, 3 ] 101 | -------------------------------------------------------------------------------- /04/01-filtering-by-value.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | const myMap = Map.of( 5 | 'one', 1, 6 | 'two', 2, 7 | 'three', 3 8 | ); 9 | 10 | // The filter callback function that's applied to each 11 | // item in the collection. If the function returns true, 12 | // the item is included in the resulting collection. 13 | const filter = i => i === 1 || i === 2; 14 | 15 | // The same filter function can be used to filter list 16 | // items and map values. 17 | const myFilteredList = myList.filter(filter); 18 | const myFilteredMap = myMap.filter(filter); 19 | 20 | console.log('myList', myList.toJS()); 21 | // -> myList [ 1, 2, 3 ] 22 | console.log('myFilteredList', myFilteredList.toJS()); 23 | // -> myFilteredList [ 1, 2 ] 24 | 25 | console.log('myMap', myMap.toJS()); 26 | // -> myMap { one: 1, two: 2, three: 3 } 27 | console.log('myFilteredMap', myFilteredMap.toJS()); 28 | // -> myFilteredMap { one: 1, two: 2 } 29 | -------------------------------------------------------------------------------- /04/02-filtering-by-greater-lesser.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | const myMap = Map.of( 5 | 'one', 1, 6 | 'two', 2, 7 | 'three', 3 8 | ); 9 | 10 | // Filters values that are less than 3. 11 | const myFilteredList = myList.filter(i => i < 3); 12 | 13 | // Filters values that are greater than 1. 14 | const myFilteredMap = myMap.filter(i => i > 1); 15 | 16 | // Uses both greater and lesser than operations to 17 | // produce a callback function that checks in the 18 | // value falls within a range. 19 | const rangeFilter = (min, max) => v => v > min && v < max; 20 | const myFilteredByRange = myList.filter(rangeFilter(1, 3)); 21 | 22 | console.log('myList', myList.toJS()); 23 | // -> myList [ 1, 2, 3 ] 24 | console.log('myFilteredList', myFilteredList.toJS()); 25 | // -> myFilteredList [ 1, 2 ] 26 | console.log('myMap', myMap.toJS()); 27 | // -> myMap { one: 1, two: 2, three: 3 } 28 | console.log('myFilteredMap', myFilteredMap.toJS()); 29 | // -> myFilteredMap { two: 2, three: 3 } 30 | console.log('myFilteredByRange', myFilteredByRange.toJS()); 31 | // -> myFilteredByRange [ 2 ] 32 | -------------------------------------------------------------------------------- /04/03-filtering-by-negation.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myMap = Map.of( 4 | 'one', 1, 5 | 'two', 2, 6 | 'three', 3 7 | ); 8 | 9 | // Returns truthy if the argument is odd. 10 | const myFilter = i => i % 2; 11 | 12 | // Filters the odd values in in the collection. 13 | const myOddsMap = myMap.filter(myFilter); 14 | // Filters the even values by negating the filter() result. 15 | const myEvensMap = myMap.filterNot(myFilter); 16 | 17 | console.log('myMap', myMap.toJS()); 18 | // -> myMap { one: 1, two: 2, three: 3 } 19 | console.log('myOddsMap', myOddsMap.toJS()); 20 | // -> myOddsMap { one: 1, three: 3 } 21 | console.log('myEvensMap', myEvensMap.toJS()); 22 | // -> myEvensMap { two: 2 } 23 | -------------------------------------------------------------------------------- /04/04-filtering-maps-by-keys.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myMap = Map.of( 4 | 'one', 1, 5 | 'two', 2, 6 | 'three', 3 7 | ); 8 | 9 | // The key of the key/value pair is the second 10 | // argument. We're looking for keys the have an "o". 11 | const myFilteredMap = myMap.filter( 12 | (v, k) => k.includes('o') 13 | ); 14 | 15 | console.log('myMap', myMap.toJS()); 16 | // -> myMap { one: 1, two: 2, three: 3 } 17 | console.log('myFilteredMap', myFilteredMap.toJS()); 18 | // -> myFilteredMap { one: 1, two: 2 } 19 | -------------------------------------------------------------------------------- /04/05-filtering-by-fancy-keys.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | // Immutable.js Map keys can be anything, including other maps. 4 | const myFancyMap = Map.of( 5 | Map.of('name', 'one'), 1, 6 | Map.of('name', 'two'), 2, 7 | Map.of('name', 'three'), 3 8 | ); 9 | 10 | // If map keys are anything fancier than strings, we might have 11 | // to filter using specific properties. In this case, we're using 12 | // the name property of the Map. 13 | const myFilteredMap = myFancyMap.filter( 14 | (v, k) => k.get('name').startsWith('t') 15 | ); 16 | 17 | console.log('myFancyMap', myFancyMap.toJS()); 18 | // -> myFancyMap 19 | // -> { 'Map { "name": "one" }': 1, 20 | // -> 'Map { "name": "two" }': 2, 21 | // -> 'Map { "name": "three" }': 3 } 22 | console.log('myFilteredMap', myFilteredMap.toJS()); 23 | // -> myFilteredMap 24 | // -> { 'Map { "name": "two" }': 2, 25 | // -> 'Map { "name": "three" }': 3 } 26 | -------------------------------------------------------------------------------- /04/06-existence-checks.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | const myMap = Map.of( 5 | 'one', 1, 6 | 'two', 2, 7 | 'three', 3 8 | ); 9 | 10 | // The has() method checks for indexes that exist in lists. 11 | const myListHas2 = myList.has(2); 12 | const myListHas3 = myList.has(3); 13 | // The has() method checks for keys that exist in maps. 14 | const myMapHasThree = myMap.has('three'); 15 | const myMapHasFour = myMap.has('four'); 16 | // The includes() method checks for values that exist in 17 | // lists or maps. 18 | const myListIncludes3 = myList.includes(3); 19 | const myListIncludes4 = myList.includes(4); 20 | const myMapIncludes3 = myMap.includes(3); 21 | const myMapIncludes4 = myMap.includes(4); 22 | 23 | console.log('myListHas2', myListHas2); 24 | // -> myListHas2 true 25 | console.log('myListHas3', myListHas3); 26 | // -> myListHas3 false 27 | 28 | console.log('myMapHasThree', myMapHasThree); 29 | // -> myMapHasThree true 30 | console.log('myMapHasFour', myMapHasFour); 31 | // -> myMapHasFour false 32 | 33 | console.log('myListIncludes3', myListIncludes3); 34 | // -> myListIncludes3 true 35 | console.log('myListIncludes4', myListIncludes4); 36 | // -> myListIncludes4 false 37 | console.log('myMapIncludes3', myMapIncludes3); 38 | // -> myMapIncludes3 true 39 | console.log('myMapIncludes4', myMapIncludes4); 40 | // -> myMapIncludes4 false 41 | -------------------------------------------------------------------------------- /04/07-finding-items.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | const myMap = Map.of( 5 | 'one', 1, 6 | 'two', 2, 7 | 'three', 3 8 | ); 9 | 10 | // Finding collection items using the find() method. The predicate 11 | // function returns true if it's the item we're looking for. 12 | // If not item is found, undefined is returned. 13 | const myFoundListItem = myList.find(v => v === 3); 14 | const myNotFoundListItem = myList.find(v => v === 4); 15 | const myFoundMapItem = myMap.find(v => v === 3); 16 | const myNotFoundMapItem = myMap.find(v => v === 4); 17 | 18 | console.log('myFoundListItem', myFoundListItem); 19 | // -> myFoundListItem 3 20 | console.log('myNotFoundListItem', myNotFoundListItem); 21 | // -> myNotFoundListItem undefined 22 | console.log('myFoundMapItem', myFoundMapItem); 23 | // -> myFoundMapItem 3 24 | console.log('myNotFoundMapItem', myNotFoundMapItem); 25 | // -> myNotFoundMapItem undefined 26 | -------------------------------------------------------------------------------- /04/08-the-is-function.js: -------------------------------------------------------------------------------- 1 | import { is, List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2); 4 | const myMap = Map.of('one', 1); 5 | 6 | // The is() function is fundamental to comparing Immutable.js 7 | // structures to other Immutable.js structures. Items are compared 8 | // deeply, and since the data is Immutable, these comparisons 9 | // can be extremely efficient. 10 | console.log('myList', myList.toJS()); 11 | // -> myList [ 1, 2 ] 12 | console.log('is([1, 2])', is(myList, List.of(1, 2))); 13 | // -> is([1, 2]) true 14 | console.log('equals([1, 2])', myList.equals(List.of(1, 2))); 15 | // -> equals([1, 2]) true 16 | console.log('is([1, 1])', is(myList, List.of(1, 1))); 17 | // -> is([1, 1]) false 18 | console.log('equals([1, 1])', myList.equals(List.of(1, 1))); 19 | // -> equals([1, 1]) false 20 | console.log('is([2, 1])', is(myList, List.of(2, 1))); 21 | // -> is([2, 1]) false 22 | console.log('equals([2, 1])', myList.equals(List.of(2, 1))); 23 | // -> equals([2, 1]) false 24 | 25 | console.log('myMap', myMap.toJS()); 26 | // -> myMap { one: 1 } 27 | console.log('is({ one: 1 })', is(myMap, Map.of('one', 1))); 28 | // -> is({ one: 1 }) true 29 | console.log('equals({ one: 1 })', myMap.equals(Map.of('one', 1))); 30 | // -> equals({ one: 1 }) true 31 | console.log('is({ two: 1 })', is(myMap, Map.of('two', 1))); 32 | // -> is({ two: 1 }) false 33 | console.log('equals({ two: 1 })', myMap.equals(Map.of('two', 1))); 34 | // -> equals({ two: 1 }) false 35 | console.log('is({ one: 2 })', is(myMap, Map.of('one', 2))); 36 | // -> is({ one: 2 }) false 37 | console.log('equals({ one: 1 })', myMap.equals(Map.of('one', 2))); 38 | // -> equals({ one: 1 }) false 39 | -------------------------------------------------------------------------------- /04/09-searching-lists-of-maps.js: -------------------------------------------------------------------------------- 1 | import { 2 | List, 3 | Map 4 | } from 'immutable'; 5 | 6 | // A utility for creating callback functions that use 7 | // Collection.equals() for equality checking. 8 | const is = a => b => a.equals(b); 9 | 10 | const myList = List.of( 11 | Map.of('one', 1), 12 | Map.of('two', 2), 13 | Map.of('three', 3) 14 | ); 15 | 16 | // The includes() method uses Immutable.js is() for equality. 17 | // We can use our is() utility function when we want the same 18 | // type of equality checking for filter operations. 19 | const includesOne = myList.includes(Map.of('one', 1)); 20 | const myFilteredList = myList.filterNot(is(Map.of('one', 1))); 21 | 22 | console.log('includesOne', includesOne); 23 | // -> includesOne true 24 | console.log('myFilteredList', myFilteredList.toJS()); 25 | // -> myFilteredList [ { two: 2 }, { three: 3 } ] 26 | -------------------------------------------------------------------------------- /04/10-partial-map-search-matches.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // This utility produces a function that compares a 4 | // map to another map. The supplied map is checked 5 | // against the map that's passed to the returned 6 | // function when called. The comparison is done 7 | // by converting both maps to lists of key/pair lists, 8 | // then using the isSubset() method. 9 | const mapIncludes = subset => superset => 10 | subset 11 | .entrySeq() 12 | .map(List) 13 | .isSubset( 14 | superset 15 | .entrySeq() 16 | .map(List) 17 | ); 18 | 19 | const myList = List.of( 20 | Map.of('one', 1, 'two', 2, 'three', 3), 21 | Map.of('one', 1, 'two', 2, 'four', 4), 22 | Map.of('one', 1, 'three', 3, 'four', 4) 23 | ); 24 | 25 | // We can pass simple maps to compare, without the need 26 | // to write a bunch of comparison logic. 27 | const filteredOneTwo = myList.filter( 28 | mapIncludes(Map.of('one', 1, 'two', 2)) 29 | ); 30 | const filteredFourFive = myList.filter( 31 | mapIncludes(Map.of('four', 4, 'five', 5)) 32 | ); 33 | 34 | console.log('myList', myList.toJS()); 35 | // -> myList 36 | // -> [ { one: 1, two: 2, three: 3 }, 37 | // -> { one: 1, two: 2, four: 4 }, 38 | // -> { one: 1, three: 3, four: 4 } ] 39 | console.log('filteredOneTwo', filteredOneTwo.toJS()); 40 | // -> filteredOneTwo 41 | // -> [ { one: 1, two: 2, three: 3 }, 42 | // -> { one: 1, two: 2, four: 4 } ] 43 | console.log('filteredFourFive', filteredFourFive.toJS()); 44 | // -> filteredFourFive [] 45 | -------------------------------------------------------------------------------- /04/11-search-direction.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of('apples', 'bananas', 'cherries', 'chocolate'); 4 | 5 | // A search predicate that looks for items that start 6 | // with "ch". 7 | const predicate = s => s.startsWith('ch'); 8 | 9 | // Finding a single item in an ordered list starting from 10 | // right to left. 11 | const myFoundItem = myList.findLast(predicate); 12 | 13 | // Filtering out a list of items from right to 14 | // left is a little trickier since we want to preserve 15 | // the left to right ordering in the result. 16 | const myFilteredList = myList.reduceRight( 17 | (result, v) => predicate(v) ? 18 | result.unshift(v) : result, 19 | List() 20 | ); 21 | 22 | console.log('myList', myList.toJS()); 23 | // -> myList [ 'apples', 'bananas', 'cherries', 'chocolate' ] 24 | console.log('myFoundItem', myFoundItem); 25 | // -> myFoundItem chocolate 26 | console.log('myFilteredList', myFilteredList.toJS()); 27 | // -> myFilteredList [ 'cherries', 'chocolate' ] 28 | -------------------------------------------------------------------------------- /05/01-basic-sequence-creation.js: -------------------------------------------------------------------------------- 1 | import { Seq } from 'immutable'; 2 | 3 | // Passing an array to Seq() creates and indexed 4 | // sequence. Passing an object to Seq() creates 5 | // a keyed sequence. 6 | const myIndexedSeq = Seq([1, 2, 3]); 7 | const myKeyedSeq = Seq({ one: 1, two: 2, three: 3 }); 8 | 9 | console.log('myIndexedSeq', myIndexedSeq.toJS()); 10 | // -> myIndexedSeq [ 1, 2, 3 ] 11 | console.log('myKeyedSeq', myKeyedSeq.toJS()); 12 | // -> myKeyedSeq { one: 1, two: 2, three: 3 } 13 | -------------------------------------------------------------------------------- /05/02-collections-to-sequences.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | const myMap = Map.of( 5 | 'one', 1, 6 | 'two', 2, 7 | 'three', 3 8 | ); 9 | 10 | // Sequences often start out as lists or maps, 11 | // and are then turned into sequences before chains 12 | // of lazy operations are called. 13 | const myIndexedSeq = myList.toSeq(); 14 | const myKeyedSeq = myMap.toSeq(); 15 | 16 | console.log('myIndexedSeq', myIndexedSeq.toJS()); 17 | // -> myIndexedSeq [ 1, 2, 3 ] 18 | console.log('myKeyedSeq', myKeyedSeq.toJS()); 19 | // -> myKeyedSeq { one: 1, two: 2, three: 3 } 20 | -------------------------------------------------------------------------------- /05/03-for-loop-iteration.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | const myMap = Map.of( 5 | 'one', 1, 6 | 'two', 2, 7 | 'three', 3 8 | ); 9 | 10 | for (let item of myList.toSeq()) { 11 | console.log('myList', item); 12 | // -> myList 1 13 | // -> myList 2 14 | // -> myList 3 15 | } 16 | 17 | for (let item of myMap.toSeq()) { 18 | console.log('myMap', item); 19 | // -> myMap [ 'one', 1 ] 20 | // -> myMap [ 'two', 2 ] 21 | // -> myMap [ 'three', 3 ] 22 | } 23 | -------------------------------------------------------------------------------- /05/04-the-for-each-iterator.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | const myMap = Map.of( 5 | 'one', 1, 6 | 'two', 2, 7 | 'three', 3 8 | ); 9 | 10 | // We go straight from a list, to iterating over 11 | // an indexed sequence. 12 | myList 13 | .toSeq() 14 | .forEach(item => console.log('myList', item)); 15 | // -> myList 1 16 | // -> myList 2 17 | // -> myList 3 18 | 19 | // We go straight from a map, to iterating over 20 | // a keyed sequence. 21 | myMap 22 | .toSeq() 23 | .forEach(item => console.log('myMap', item)); 24 | // -> myMap 1 25 | // -> myMap 2 26 | // -> myMap 3 27 | -------------------------------------------------------------------------------- /05/05-sequence-filters.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3, 4, 5, 6); 4 | const myMap = Map.of( 5 | 'one', 1, 'two', 2, 6 | 'three', 3, 'four', 4, 7 | 'five', 5, 'six', 6 8 | ); 9 | 10 | // A simple filter predicate that will return 11 | // odd numbers. We're logging the call here 12 | // so that we can see where it's called relative 13 | // to the forEach() callback. 14 | const predicate = (item) => { 15 | console.log('filtering', item); 16 | return item % 2; 17 | }; 18 | 19 | // By converting to a sequence before filtering, 20 | // we enable lazy evaluation. The forEach() callback 21 | // "pulls" one item through the sequence at a time. 22 | myList 23 | .toSeq() 24 | .filter(predicate) 25 | .forEach(item => console.log('myList', item)); 26 | // -> filtering 1 27 | // -> myList 1 28 | // -> filtering 2 29 | // -> filtering 3 30 | // -> myList 3 31 | // -> filtering 4 32 | // -> filtering 5 33 | // -> myList 5 34 | // -> filtering 6 35 | 36 | // We get identical results with maps. 37 | myMap 38 | .toSeq() 39 | .filter(predicate) 40 | .forEach(item => console.log('myMap', item)); 41 | // -> filtering 1 42 | // -> myMap 1 43 | // -> filtering 2 44 | // -> filtering 3 45 | // -> myMap 3 46 | // -> filtering 4 47 | // -> filtering 5 48 | // -> myMap 5 49 | // -> filtering 6 50 | -------------------------------------------------------------------------------- /05/06-multiple-sequence-filters.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | // Equality predicate builder. Includes output 4 | // of comparisons as they're made so we can see 5 | // them relative to the output. 6 | const equals = first => (second) => { 7 | console.log(`${first} === ${second}`); 8 | return first === second; 9 | }; 10 | 11 | const myList = List.of(1, 2, 3, 4, 5, 6); 12 | 13 | // It's easy to assemble complex filtering logic, 14 | // without having to create intermediary representations. 15 | // The filters are executed one at a time, in order, 16 | // for each item in the collection. 17 | myList 18 | .toSeq() 19 | .filterNot(equals(1)) 20 | .filterNot(equals(3)) 21 | .filterNot(equals(5)) 22 | .forEach(item => console.log('myList', item)); 23 | // -> 1 === 1 24 | // -> 1 === 2 25 | // -> 3 === 2 26 | // -> 5 === 2 27 | // -> myList 2 28 | // -> 1 === 3 29 | // -> 3 === 3 30 | // -> 1 === 4 31 | // -> 3 === 4 32 | // -> 5 === 4 33 | // -> myList 4 34 | // -> 1 === 5 35 | // -> 3 === 5 36 | // -> 5 === 5 37 | // -> 1 === 6 38 | // -> 3 === 6 39 | // -> 5 === 6 40 | // -> myList 6 41 | -------------------------------------------------------------------------------- /05/07-removing-work-with-take.js: -------------------------------------------------------------------------------- 1 | import { Range } from 'immutable'; 2 | 3 | // Filter to include odd numbers 4 | const predicate = i => i % 2; 5 | 6 | // An infinate range sequence. 7 | const myRange = Range(); 8 | 9 | // The resulting forEach() iteratee will always 10 | // be called 5 times, since take() is applied 11 | // after filter(). 12 | myRange 13 | .filter(predicate) 14 | .take(5) 15 | .forEach((item) => { 16 | console.log('myRange (filter then take)', item); 17 | }); 18 | // -> myRange (filter then take) 1 19 | // -> myRange (filter then take) 3 20 | // -> myRange (filter then take) 5 21 | // -> myRange (filter then take) 7 22 | // -> myRange (filter then take) 9 23 | 24 | // 5 items are taken from the sequence before 25 | // the filter() is applied. This means that the 26 | // forEach() iteratee could be called fewer than 27 | // 5 times. 28 | myRange 29 | .take(5) 30 | .filter(predicate) 31 | .forEach((item) => { 32 | console.log('myRange (take then filter)', item); 33 | }); 34 | // -> myRange (take then filter) 1 35 | // -> myRange (take then filter) 3 36 | -------------------------------------------------------------------------------- /05/08-pagination-using-slice.js: -------------------------------------------------------------------------------- 1 | import { Range } from 'immutable'; 2 | 3 | // Filter to include even numbers 4 | const predicate = i => i % 2 === 0; 5 | 6 | // An infinite sequence of even numbers. 7 | const myRange = Range().filter(predicate); 8 | 9 | // Using slice() to create "pages" from our 10 | // infinite number sequence. 11 | const page1 = myRange.slice(0, 5); 12 | const page2 = myRange.slice(5, 10); 13 | const page3 = myRange.slice(10, 15); 14 | 15 | // The slice() and the filter() calls aren't applied 16 | // until we iterate over each page. 17 | page1.forEach(i => console.log('page1', i)); 18 | // -> page1 0 19 | // -> page1 2 20 | // -> page1 4 21 | // -> page1 6 22 | // -> page1 8 23 | 24 | page2.forEach(i => console.log('page2', i)); 25 | // -> page2 10 26 | // -> page2 12 27 | // -> page2 14 28 | // -> page2 16 29 | // -> page2 18 30 | 31 | page3.forEach(i => console.log('page3', i)); 32 | // -> page3 20 33 | // -> page3 22 34 | // -> page3 24 35 | // -> page3 26 36 | // -> page3 28 37 | -------------------------------------------------------------------------------- /06/01-deafult-sorting.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of(2, 1, 4, 3); 4 | 5 | // This works the same way as default sorting 6 | // with JavaScript arrays. The main difference is 7 | // that this creates a new list. 8 | const mySortedList = myList.sort(); 9 | 10 | console.log('myList', myList.toJS()); 11 | // -> myList [ 2, 1, 4, 3 ] 12 | 13 | console.log('mySortedList', mySortedList.toJS()); 14 | // -> mySortedList [ 1, 2, 3, 4 ] 15 | -------------------------------------------------------------------------------- /06/02-reversed-sorting.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of(2, 1, 4, 3); 4 | 5 | // Reversing does exactly that. It reverses the current 6 | // order of the list items. So unless you're absolutely 7 | // certain about the current sort order, this 8 | // method could have unintended consequences. 9 | const myReversedList = myList.reverse(); 10 | 11 | // If we want the expected behavior of reverse(), 12 | // we have to sort() the collection first. 13 | const mySortedReversedList = myList.sort().reverse(); 14 | 15 | // Sorting backwards will actually perform a sort, 16 | // instead of just reversing the current order. 17 | const myBackwardSortedList = myList.sortBy(i => -i); 18 | 19 | console.log('myList', myList.toJS()); 20 | // -> myList [ 2, 1, 4, 3 ] 21 | 22 | console.log('myReversedList', myReversedList.toJS()); 23 | // -> myReversedList [ 3, 4, 1, 2 ] 24 | 25 | console.log('mySortedReversedList', mySortedReversedList.toJS()); 26 | // -> mySortedReversedList [ 4, 3, 2, 1 ] 27 | 28 | console.log('myBackwardSortedList', myBackwardSortedList.toJS()); 29 | // -> myBackwardSortedList [ 4, 3, 2, 1 ] 30 | -------------------------------------------------------------------------------- /06/03-sorting-lists-of-maps.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // Utility to create a simple sortBy() callback. 4 | const prop = p => map => map.get(p); 5 | 6 | // Utility to create a sort() callback. This is useful 7 | // when we need to sort lists of maps by more than one 8 | // property. We keep iterating over the properties 9 | // until we find two that aren't equal. 10 | const comp = (order, ...props) => (a, b) => { 11 | for (const p of props) { 12 | if (a.get(p) > b.get(p)) { 13 | return order * 1; 14 | } 15 | 16 | if (a.get(p) < b.get(p)) { 17 | return order * -1; 18 | } 19 | } 20 | return 0; 21 | }; 22 | 23 | const asc = (...props) => comp(1, ...props); 24 | const desc = (...props) => comp(-1, ...props); 25 | 26 | const myList = List.of( 27 | Map.of('name', 'ccc', 'age', 23), 28 | Map.of('name', 'aaa', 'age', 23), 29 | Map.of('name', 'ddd', 'age', 19), 30 | Map.of('name', 'bbb', 'age', 19) 31 | ); 32 | 33 | // Sorting by a single property, using sortBy() and prop(). 34 | const byAge = myList 35 | .sortBy(prop('age')); 36 | 37 | // Sorting by multiple properties, using sort() and asc(). 38 | const byAgeAndName = myList 39 | .sort(asc('age', 'name')); 40 | 41 | // Sorting by multiple properties, using sort() and desc(). 42 | const byAgeAndNameDesc = myList 43 | .sort(desc('age', 'name')); 44 | 45 | console.log('myList', myList.toJS()); 46 | // -> myList 47 | // -> [ { name: 'ccc', age: 23 }, 48 | // -> { name: 'aaa', age: 23 }, 49 | // -> { name: 'ddd', age: 19 }, 50 | // -> { name: 'bbb', age: 19 } ] 51 | 52 | console.log('byAge', byAge.toJS()); 53 | // -> byAge 54 | // -> [ { name: 'ddd', age: 19 }, 55 | // -> { name: 'bbb', age: 19 }, 56 | // -> { name: 'ccc', age: 23 }, 57 | // -> { name: 'aaa', age: 23 } ] 58 | 59 | console.log('byAgeAndName', byAgeAndName.toJS()); 60 | // -> byAgeAndName [ { name: 'ccc', age: 23 } 61 | // -> { name: 'aaa', age: 23 }, 62 | // -> { name: 'ddd', age: 19 }, 63 | // -> { name: 'bbb', age: 19 } ] 64 | 65 | console.log('byAgeAndNameDesc', byAgeAndNameDesc.toJS()); 66 | // -> byAgeAndNameDesc [ { name: 'bbb', age: 19 }, 67 | // -> { name: 'ddd', age: 19 }, 68 | // -> { name: 'aaa', age: 23 }, 69 | // -> { name: 'ccc', age: 23 } ] 70 | -------------------------------------------------------------------------------- /06/04-ordering-maps.js: -------------------------------------------------------------------------------- 1 | import { Map, OrderedMap } from 'immutable'; 2 | 3 | // Maps generally maintain the set() order, but this 4 | // isn't guaranteed during iteration. 5 | const myMap = Map() 6 | .set('one', 1) 7 | .set('two', 2) 8 | .set('three', 3); 9 | 10 | myMap 11 | .forEach(i => console.log('myMap', i)); 12 | // -> myMap 1 13 | // -> myMap 2 14 | // -> myMap 3 15 | 16 | // Ordered maps enforce set() order, including the 17 | // iteration order of items. 18 | const myOrderedMap = OrderedMap() 19 | .set('three', 3) 20 | .set('two', 2) 21 | .set('one', 1); 22 | 23 | myOrderedMap 24 | .forEach(i => console.log('myOrderedMap', i)); 25 | // -> myOrderedMap 3 26 | // -> myOrderedMap 2 27 | // -> myOrderedMap 1 28 | -------------------------------------------------------------------------------- /06/05-sorting-maps.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myMap = Map.of( 4 | 'three', 3, 5 | 'one', 1, 6 | 'four', 4, 7 | 'two', 2 8 | ); 9 | 10 | // We can sort maps using the sort() method, but 11 | // to preserve the sort order, we need to convert 12 | // it to an ordered map. 13 | const mySortedMap = myMap 14 | .toOrderedMap() 15 | .sort(); 16 | 17 | // Using the sort() method sorts maps by value. Using 18 | // sortBy(), we can pass a simple callback that allows 19 | // us to sort by key. 20 | const mySortedByKeyMap = myMap 21 | .toOrderedMap() 22 | .sortBy((v, k) => k); 23 | 24 | myMap.forEach( 25 | (v, k) => console.log('myMap', `${k} => ${v}`) 26 | ); 27 | // -> myMap three => 3 28 | // -> myMap one => 1 29 | // -> myMap four => 4 30 | // -> myMap two => 2 31 | 32 | 33 | mySortedMap.forEach( 34 | (v, k) => console.log('mySortedMap', `${k} => ${v}`) 35 | ); 36 | // -> mySortedMap one => 1 37 | // -> mySortedMap two => 2 38 | // -> mySortedMap three => 3 39 | // -> mySortedMap four => 4 40 | 41 | mySortedByKeyMap.forEach( 42 | (v, k) => console.log('mySortedByKeyMap', `${k} => ${v}`) 43 | ); 44 | // -> mySortedByKeyMap four => 4 45 | // -> mySortedByKeyMap one => 1 46 | // -> mySortedByKeyMap three => 3 47 | // -> mySortedByKeyMap two => 2 48 | -------------------------------------------------------------------------------- /06/06-maintaining-sort-order.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | // With a given item and a given list, we can 4 | // figure out the insertion index that would 5 | // maintain sort order of the list, then use 6 | // the insert() method to insert the item in 7 | // the correct order. 8 | const sortedInsert = (item, list) => { 9 | let low = 0; 10 | let high = list.count(); 11 | 12 | while (low < high) { 13 | const mid = (low + high) >>> 1; 14 | if (item > list.get(mid)) { 15 | low = mid + 1; 16 | } else { 17 | high = mid; 18 | } 19 | } 20 | 21 | return list.insert(low, item); 22 | }; 23 | 24 | const myList = List.of(1, 3, 5); 25 | const withTwo = sortedInsert(2, myList); 26 | const withFour = sortedInsert(4, withTwo); 27 | const usingSort = myList 28 | .push(2) 29 | .push(4) 30 | .sort(); 31 | 32 | console.log('myList', myList.toJS()); 33 | // -> myList [ 1, 3, 5 ] 34 | console.log('withTwo', withTwo.toJS()); 35 | // -> withTwo [ 1, 2, 3, 5 ] 36 | console.log('withFour', withFour.toJS()); 37 | // -> withFour [ 1, 2, 3, 4, 5 ] 38 | console.log('usingSort', usingSort.toJS()); 39 | // -> usingSort [ 1, 2, 3, 4, 5 ] 40 | -------------------------------------------------------------------------------- /07/01-plucking-property-values.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of( 4 | Map.of('first', 1, 'second', 2), 5 | Map.of('first', 3, 'second', 4), 6 | Map.of('first', 5, 'second', 6) 7 | ); 8 | 9 | // Mapping Immutable.js lists works the same as 10 | // mapping JavaScript arrays. Here, we're producing 11 | // a new list and we're using get() for each map 12 | // item in the list to get the value we're after. 13 | const myMappedList = myList 14 | .map(v => v.get('second')); 15 | 16 | console.log('myList', myList.toJS()); 17 | // -> myList [ 18 | // -> { first: 1, second: 2 }, 19 | // -> { first: 3, second: 4 }, 20 | // -> { first: 5, second: 6 } ] 21 | 22 | console.log('myMappedList', myMappedList.toJS()); 23 | // -> myMappedList [ 2, 4, 6 ] 24 | -------------------------------------------------------------------------------- /07/02-computing-new-values.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // Capitalizes the first letter of a given string. 4 | const capitalize = s => 5 | `${s.charAt(0).toUpperCase()}${s.slice(1)}`; 6 | 7 | const myList = List.of( 8 | Map.of('first', 'joe', 'last', 'brown', 'age', 45), 9 | Map.of('first', 'john', 'last', 'smith', 'age', 32), 10 | Map.of('first', 'mary', 'last', 'wise', 'age', 56) 11 | ); 12 | 13 | // Instead of mapping the list item to a value 14 | // that already exists within the item, we're 15 | // computing a new value. In this case, we need 16 | // the capitalized name string instead of two 17 | // string properties. 18 | const myMappedList = myList.map( 19 | v => [ 20 | capitalize(v.get('first')), 21 | capitalize(v.get('last')) 22 | ].join(' ') 23 | ); 24 | 25 | console.log('myList', myList.toJS()); 26 | // -> myList [ { first: 'joe', last: 'brown', age: 45 }, 27 | // -> { first: 'john', last: 'smith', age: 32 }, 28 | // -> { first: 'mary', last: 'wise', age: 56 } ] 29 | 30 | console.log('myMappedList', myMappedList.toJS()); 31 | // -> myMappedList [ 'Joe Brown', 'John Smith', 'Mary Wise' ] 32 | -------------------------------------------------------------------------------- /07/03-mapping-new-properties.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // Capitalizes the first letter of a given string. 4 | const capitalize = s => 5 | `${s.charAt(0).toUpperCase()}${s.slice(1)}`; 6 | 7 | const myList = List.of( 8 | Map.of('first', 'joe', 'last', 'brown', 'age', 45), 9 | Map.of('first', 'john', 'last', 'smith', 'age', 32), 10 | Map.of('first', 'mary', 'last', 'wise', 'age', 56) 11 | ); 12 | 13 | // Instead of mapping the list of maps to a list 14 | // of name strings, we map it to a new list of maps. 15 | // We're simply adding a new name property to each 16 | // item. 17 | const myMappedList = myList.map( 18 | v => v.set('name', [ 19 | capitalize(v.get('first')), 20 | capitalize(v.get('last')) 21 | ].join(' ')) 22 | ); 23 | 24 | console.log('myList', myList.toJS()); 25 | // -> myList [ { first: 'joe', last: 'brown', age: 45 }, 26 | // -> { first: 'john', last: 'smith', age: 32 }, 27 | // -> { first: 'mary', last: 'wise', age: 56 } ] 28 | 29 | console.log('myMappedList', myMappedList.toJS()); 30 | // -> myMappedList [ { first: 'joe', last: 'brown', age: 45, name: 'Joe Brown' }, 31 | // -> { first: 'john', last: 'smith', age: 32, name: 'John Smith' }, 32 | // -> { first: 'mary', last: 'wise', age: 56, name: 'Mary Wise' } ] 33 | -------------------------------------------------------------------------------- /07/04-mapping-specific-properties.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // Returns a new map that only includes the given 4 | // property names. 5 | const pick = (...props) => map => map 6 | .filter((v, k) => props.includes(k)); 7 | 8 | // Returns a new map that omits the given 9 | // property names. 10 | const omit = (...props) => map => map 11 | .filterNot((v, k) => props.includes(k)); 12 | 13 | const myList = List.of( 14 | Map.of('first', 'joe', 'last', 'brown', 'age', 45), 15 | Map.of('first', 'john', 'last', 'smith', 'age', 32), 16 | Map.of('first', 'mary', 'last', 'wise', 'age', 56) 17 | ); 18 | 19 | // Using pick() and omit() as higher-order functions 20 | // to create callbacks for map() to filter specific 21 | // property names. 22 | const myPickedList = myList.map(pick('first', 'last')); 23 | const myOmittedList = myList.map(omit('first', 'last')); 24 | 25 | console.log('myList', myList.toJS()); 26 | // -> myList [ { first: 'joe', last: 'brown', age: 45 }, 27 | // -> { first: 'john', last: 'smith', age: 32 }, 28 | // -> { first: 'mary', last: 'wise', age: 56 } ] 29 | 30 | console.log('myPickedList', myPickedList.toJS()); 31 | // -> myPickedList [ { first: 'joe', last: 'brown' }, 32 | // -> { first: 'john', last: 'smith' }, 33 | // -> { first: 'mary', last: 'wise' } ] 34 | 35 | console.log('myOmittedList', myOmittedList.toJS()); 36 | // -> myOmittedList [ { age: 45 }, 37 | // -> { age: 32 }, 38 | // -> { age: 56 } ] 39 | -------------------------------------------------------------------------------- /07/05-reducing-max-min.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of( 4 | Map.of('red', 3, 'black', 4), 5 | Map.of('red', 6, 'black', 8), 6 | Map.of('red', 9, 'black', 12) 7 | ); 8 | 9 | // We can call reduce() directly to produce the minimum 10 | // and maximum values that we need. These are simple, 11 | // but a little redundant. The only difference between 12 | // the two is the operator. 13 | const minRedReduce = myList.reduce( 14 | (result, v) => v.get('red') < result ? 15 | v.get('red') : 16 | result, 17 | myList.first().get('red') 18 | ); 19 | const maxRedReduce = myList.reduce( 20 | (result, v) => v.get('red') > result ? 21 | v.get('red') : 22 | result, 23 | myList.first().get('red') 24 | ); 25 | 26 | // Another approach is to map the list to 27 | // all it's red values, then use the min() and 28 | // the max() functions to get the correponding 29 | // values. 30 | const minRedMap = myList 31 | .map(v => v.get('red')) 32 | .min(); 33 | const maxRedMap = myList 34 | .map(v => v.get('red')) 35 | .max(); 36 | 37 | // The minBy() and maxBy() functions combine the 38 | // map and reduce into one step. However, since this results 39 | // in the full object, we have to call get() if we only 40 | // want the number. 41 | const minByRed = myList 42 | .minBy(v => v.get('red')) 43 | .get('red'); 44 | const maxByRed = myList 45 | .maxBy(v => v.get('red')) 46 | .get('red'); 47 | 48 | console.log('minRedReduce', minRedReduce); 49 | // -> minRedReduce 3 50 | console.log('maxRedReduce', maxRedReduce); 51 | // -> maxRedReduce 9 52 | console.log('minRedMap', minRedMap); 53 | // -> minRedMap 3 54 | console.log('maxRedMap', maxRedMap); 55 | // -> maxRedMap 9 56 | console.log('minByRed', minByRed); 57 | // -> minByRed 3 58 | console.log('maxByRed', maxByRed); 59 | // -> maxByRed 9 60 | -------------------------------------------------------------------------------- /07/06-accumulating-values.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of( 4 | Map.of('red', 3, 'black', 4), 5 | Map.of('red', 6, 'black', 8), 6 | Map.of('red', 9, 'black', 12) 7 | ); 8 | 9 | // Reducing a list of maps to accumulate a specific 10 | // property is reasy. It can also be repetitive and 11 | // ineficient if we need more than one property. 12 | const redSum = myList.reduce( 13 | (result, v) => result + v.get('red'), 14 | 0 15 | ); 16 | const blackSum = myList.reduce( 17 | (result, v) => result + v.get('black'), 18 | 0 19 | ); 20 | 21 | // This approach reduces the list of maps to a single map, 22 | // instead of a single number. This avoids iterating the 23 | // entire list more than once. 24 | const groupedSums = myList 25 | .reduce((result, v) => result 26 | .update('red', r => r + v.get('red')) 27 | .update('black', b => b + v.get('black')) 28 | ); 29 | 30 | console.log('myList', myList.toJS()); 31 | // -> myList [ { red: 3, black: 4 }, 32 | // -> { red: 6, black: 8 }, 33 | // -> { red: 9, black: 12 } ] 34 | 35 | console.log('redSum', redSum); 36 | // -> redSum 18 37 | 38 | console.log('blackSum', blackSum); 39 | // -> blackSum 24 40 | 41 | console.log('groupedSums.red', groupedSums.get('red')); 42 | // -> groupedSums.red 18 43 | 44 | console.log('groupedSums.black', groupedSums.get('black')); 45 | // -> groupedSums.black 24 46 | -------------------------------------------------------------------------------- /07/07-multiple-map-calls.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // Capitalizes the first letter of a given string. 4 | const capitalize = s => 5 | `${s.charAt(0).toUpperCase()}${s.slice(1)}`; 6 | 7 | const myList = List.of( 8 | Map.of('first', 'joe', 'last', 'brown', 'age', 45), 9 | Map.of('first', 'john', 'last', 'smith', 'age', 32), 10 | Map.of('first', 'mary', 'last', 'wise', 'age', 56) 11 | ); 12 | 13 | // Maps the list of maps to a list of strings. Instead of 14 | // doing everything in a single map() call, we create 15 | // three specialized map() calls that do one specific thing. 16 | // Since this is a sequence, splitting up map() calls has 17 | // no impact on performance. 18 | myList 19 | .toSeq() 20 | .map(v => v.update('first', capitalize)) 21 | .map(v => v.update('last', capitalize)) 22 | .map(v => [v.get('first'), v.get('last')].join(' ')) 23 | .forEach(v => console.log('name', v)); 24 | // -> name Joe Brown 25 | // -> name John Smith 26 | // -> name Mary Wise 27 | -------------------------------------------------------------------------------- /07/08-filtering-before-mapping.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // Capitalizes the first letter of a given string. 4 | const capitalize = s => 5 | `${s.charAt(0).toUpperCase()}${s.slice(1)}`; 6 | 7 | const myList = List.of( 8 | Map.of('first', 'joe', 'last', 'brown', 'age', 45), 9 | Map.of('first', 'john', 'last', 'smith', 'age', 32), 10 | Map.of('first', 'mary', 'last', 'wise', 'age', 56) 11 | ); 12 | 13 | // Lazily filtering and mapping. When an item is found, 14 | // it is passed through each map() call before filtering 15 | // is continued. 16 | myList 17 | .toSeq() 18 | .filter(v => v.get('age') > 35) 19 | .map(v => v.update('first', capitalize)) 20 | .map(v => v.update('last', capitalize)) 21 | .map(v => [v.get('first'), v.get('last')].join(' ')) 22 | .forEach(v => console.log('name', v)); 23 | // -> name Joe Brown 24 | // -> name Mary Wise 25 | -------------------------------------------------------------------------------- /07/09-filter-map-reduce.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // Capitalizes the first letter of a given string. 4 | const capitalize = s => 5 | `${s.charAt(0).toUpperCase()}${s.slice(1)}`; 6 | 7 | // Picks an array of of property values 8 | // from a map. 9 | const pick = (m, ...props) => 10 | props.map(p => m.get(p)); 11 | 12 | const myList = List.of( 13 | Map.of('first', 'joe', 'last', 'brown', 'age', 45), 14 | Map.of('first', 'john', 'last', 'smith', 'age', 32), 15 | Map.of('first', 'mary', 'last', 'wise', 'age', 56) 16 | ); 17 | 18 | console.log('myList', myList.toJS()); 19 | // -> myList [ { first: 'joe', last: 'brown', age: 45 }, 20 | // -> { first: 'john', last: 'smith', age: 32 }, 21 | // -> { first: 'mary', last: 'wise', age: 56 } ] 22 | 23 | // Filter, map reduce, side-effects. This is a common 24 | // pattern with immutable data. The best part is that 25 | // any given step is optional, 26 | myList 27 | .toSeq() 28 | .filter(v => v.get('age') < 50) 29 | .map(v => v.update('first', capitalize)) 30 | .map(v => v.update('last', capitalize)) 31 | .map(v => v.set('name', pick(v, 'first', 'last').join(' '))) 32 | .reduce( 33 | (result, v) => result 34 | .update('age', a => a + v.get('age')) 35 | .update('names', n => n.push(v.get('name'))), 36 | Map.of('names', List(), 'age', 0) 37 | ) 38 | .forEach( 39 | (v, k) => console.log(k, List.isList(v) ? v.toJS() : v) 40 | ); 41 | // -> names [ 'Joe Brown', 'John Smith' ] 42 | // -> age 77 43 | -------------------------------------------------------------------------------- /08/01-zipping-lists-of-simple-values.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const names = List.of('Jeremy', 'Garry', 'Katie'); 4 | const roles = List.of('Engineer', 'Designer', 'Programmer'); 5 | const ages = List.of(34, 23, 36); 6 | 7 | // Zips together the names, roles, and ages lists. 8 | // The argument passed to map is an array of items 9 | // with corresponding indexes from each list. 10 | names 11 | .zip(roles, ages) 12 | .map(v => v.join(', ')) 13 | .forEach(v => console.log('employee', v)); 14 | // -> employee Jeremy, Engineer, 34 15 | // -> employee Garry, Designer, 23 16 | // -> employee Katie, Programmer, 36 17 | -------------------------------------------------------------------------------- /08/02-zipping-lists-of-maps.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const names = List.of( 4 | Map.of('name', 'Jeremy'), 5 | Map.of('name', 'Garry'), 6 | Map.of('name', 'Katie') 7 | ); 8 | const roles = List.of( 9 | Map.of('role', 'Engineer'), 10 | Map.of('role', 'Designer'), 11 | Map.of('role', 'Programmer') 12 | ); 13 | const ages = List.of( 14 | Map.of('age', 34), 15 | Map.of('age', 23), 16 | Map.of('age', 36) 17 | ); 18 | 19 | // Each item in the lists that we're zipping together 20 | // is a map. This means that we can use zipWith() to 21 | // merge the maps together as they're zipped. 22 | names 23 | .zipWith( 24 | (...colls) => Map().merge(...colls), 25 | roles, 26 | ages 27 | ) 28 | .forEach(v => console.log('employee', v.toJS())); 29 | // -> employee { name: 'Jeremy', role: 'Engineer', age: 34 } 30 | // -> employee { name: 'Garry', role: 'Designer', age: 23 } 31 | // -> employee { name: 'Katie', role: 'Programmer', age: 36 } 32 | -------------------------------------------------------------------------------- /08/03-lazy-zipping.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const names = List.of( 4 | Map.of('name', 'Jeremy'), 5 | Map.of('name', 'Garry'), 6 | Map.of('name', 'Katie') 7 | ); 8 | const roles = List.of( 9 | Map.of('role', 'Engineer'), 10 | Map.of('role', 'Designer'), 11 | Map.of('role', 'Programmer') 12 | ); 13 | const ages = List.of( 14 | Map.of('age', 34), 15 | Map.of('age', 23), 16 | Map.of('age', 36) 17 | ); 18 | 19 | // When we call zipWith() on a sequence, the merges are 20 | // performed lazily. 21 | names 22 | .toSeq() 23 | .zipWith( 24 | (...maps) => Map().merge(...maps), 25 | roles, 26 | ages 27 | ) 28 | .forEach(v => console.log('lazy zipWith', v.toJS())); 29 | // -> lazy zipWith { name: 'Jeremy', role: 'Engineer', age: 34 } 30 | // -> lazy zipWith { name: 'Garry', role: 'Designer', age: 23 } 31 | // -> lazy zipWith { name: 'Katie', role: 'Programmer', age: 36 } 32 | 33 | // This works the same way as zipWith(), and everything 34 | // is evaluated lazily as well. It's just a different 35 | // approach to how the solution is structured. 36 | names 37 | .toSeq() 38 | .zip(roles, ages) 39 | .map(maps => Map().merge(...maps)) 40 | .forEach(v => console.log('lazy zip', v.toJS())); 41 | // -> lazy zip { name: 'Jeremy', role: 'Engineer', age: 34 } 42 | // -> lazy zip { name: 'Garry', role: 'Designer', age: 23 } 43 | // -> lazy zip { name: 'Katie', role: 'Programmer', age: 36 } 44 | -------------------------------------------------------------------------------- /08/04-deep-flattening-and-filtering.js: -------------------------------------------------------------------------------- 1 | import { fromJS } from 'immutable'; 2 | 3 | const myList = fromJS([ 4 | [1, 2], 5 | [3, 4], 6 | [ 7 | [5, 6], 8 | [ 9 | [7, 8], 10 | [9, 10], 11 | [ 12 | [11, 12], 13 | [13, 14], 14 | [ 15 | [15, 16], 16 | [17, 18], 17 | [ 18 | [19, 20] 19 | ] 20 | ] 21 | ] 22 | ] 23 | ] 24 | ]); 25 | 26 | console.log('myList', myList.flatten().toJS()); 27 | // -> myList [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 28 | // -> 11, 12, 13, 14, 15, 16, 17, 18, 29 | // -> 19, 20 ] 30 | 31 | // We're looking for odd numbers in a deeply-nested 32 | // list of numbers. If we convert the list to a sequence, 33 | // then flatten it, we can avoid recursion, and flatten 34 | // the list lazily. 35 | myList 36 | .toSeq() 37 | .flatten() 38 | .filter(v => v % 2) 39 | .take(5) 40 | .forEach(v => console.log('odd', v)); 41 | // -> odd 1 42 | // -> odd 3 43 | // -> odd 5 44 | // -> odd 7 45 | // -> odd 9 46 | -------------------------------------------------------------------------------- /08/05-shallow-flattening.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of( 4 | List.of('first', 'second'), 5 | List.of('third', 'fourth', List.of('fifth')), 6 | List.of('sixth', 'seventh') 7 | ); 8 | 9 | // Lists are recursed and flattened deeply by default. 10 | // This could be a performance hit, and have unintended 11 | // consequences. In this case, "fifth" is included even 12 | // though we don't want it. 13 | const myFlattenedList = myList.flatten(); 14 | 15 | // The true argument passed to flatten() tells it 16 | // to stay shallow. This way, the list that wraps 17 | // the "fifth" string isn't flattened, and it's 18 | // easy to filter out. 19 | const myShallowList = myList 20 | .flatten(true) 21 | .filterNot(List.isList); 22 | 23 | console.log('myList', myList.toJS()); 24 | // -> myList [ [ 'first', 'second' ], 25 | // -> [ 'third', 'fourth', [ 'fifth' ] ], 26 | // -> [ 'sixth', 'seventh' ] ] 27 | 28 | console.log('myFlattenedList', myFlattenedList.toJS()); 29 | // -> myFlattenedList [ 'first', 'second', 'third', 'fourth', 30 | // -> 'fifth', 'sixth', 'seventh' ] 31 | 32 | console.log('myShallowList', myShallowList.toJS()); 33 | // -> myShallowList [ 'first', 'second', 'third', 'fourth', 34 | // -> 'sixth', 'seventh' ] 35 | -------------------------------------------------------------------------------- /08/06-flattening-nested-maps.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myMap = Map.of( 4 | 'first', 1, 5 | 'second', Map.of( 6 | 'third', 3, 7 | 'fourth', 4, 8 | 'fifth', Map.of( 9 | 'sixth', Map.of( 10 | 'seventh', 7 11 | ) 12 | ) 13 | ) 14 | ); 15 | 16 | console.log('myMap', myMap.toJS()); 17 | // -> { first: 1, 18 | // -> second: { 19 | // -> third: 3, 20 | // -> fourth: 4, 21 | // -> fifth: { 22 | // -> sixth: { seventh: 7 } } } } 23 | 24 | // When we flatten a map, we want a new map containing 25 | // only keys that have primitive values. In other words, 26 | // keys with Maps as values are recursively removed. 27 | myMap 28 | .toSeq() 29 | .flatten() 30 | .filter((v, k) => k.startsWith('f')) 31 | .forEach((v, k) => console.log(k, v)); 32 | // -> first 1 33 | // -> fourth 4 34 | -------------------------------------------------------------------------------- /09/01-strict-equality.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | const myMap = Map.of( 5 | 'one', 1, 6 | 'two', 2, 7 | 'three', 3 8 | ); 9 | 10 | // Modifies the list by pushing a new item. 11 | const myModifiedList = myList.push(4); 12 | // Modifies the map by setting a new key/value. 13 | const myModifiedMap = myMap.set('four', 4); 14 | // Returns the same instance of the list because 15 | // the first item is already 1. 16 | const myUnmodifiedList = myList.set(0, 1); 17 | // Returns the same instance of the map becuase 18 | // this key/value already exists. 19 | const myUnmodifiedMap = myMap.set('one', 1); 20 | 21 | console.log('myList', myList.toJS()); 22 | // -> myList [ 1, 2, 3 ] 23 | console.log('myMap', myMap.toJS()); 24 | // -> myMap { one: 1, two: 2, three: 3 } 25 | console.log('myModifiedList', myModifiedList.toJS()); 26 | // -> myModifiedList [ 1, 2, 3, 4 ] 27 | console.log('myModifiedMap', myModifiedMap.toJS()); 28 | // -> myModifiedMap { one: 1, two: 2, three: 3, four: 4 } 29 | console.log('myUnmodifiedList', myUnmodifiedList.toJS()); 30 | // -> myUnmodifiedList [ 1, 2, 3 ] 31 | console.log('myUnmodifiedMap', myUnmodifiedMap.toJS()); 32 | // -> myUnmodifiedMap { one: 1, two: 2, three: 3 } 33 | console.log( 34 | 'myList === myModifiedList', 35 | myList === myModifiedList 36 | ); 37 | // -> myList === myModifiedList false 38 | console.log( 39 | 'myMap === myModifiedMap', 40 | myMap === myModifiedMap 41 | ); 42 | // -> myMap === myModifiedMap false 43 | console.log( 44 | 'myList === myUnmodifiedList', 45 | myList === myUnmodifiedList 46 | ); 47 | // -> myList === myUnmodifiedList true 48 | console.log( 49 | 'myMap === myUnmodifiedMap', 50 | myMap === myUnmodifiedMap 51 | ); 52 | // -> myMap === myUnmodifiedMap true 53 | -------------------------------------------------------------------------------- /09/02-strict-equality-vs-equals.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myMap = Map.of('first', 1); 4 | const myModifiedMap = myMap.set('first', 'first'); 5 | const myUnmodifiedMap = myMap.set('first', 1); 6 | 7 | console.log('myMap', myMap.toJS()); 8 | // -> myMap { first: 1 } 9 | console.log('myModifiedMap', myModifiedMap.toJS()); 10 | // -> myModifiedMap { first: 'first' } 11 | console.log('myUnmodifiedMap', myUnmodifiedMap.toJS()); 12 | // -> myUnmodifiedMap { first: 1 } 13 | console.log( 14 | 'myMap === myModifiedMap', 15 | myMap === myModifiedMap 16 | ); 17 | // -> myMap === myModifiedMap false 18 | console.log( 19 | 'myMap === myUnmodifiedMap', 20 | myMap === myUnmodifiedMap 21 | ); 22 | // -> myMap === myUnmodifiedMap true 23 | console.log( 24 | 'myMap.equals(myModifiedMap)', 25 | myMap.equals(myModifiedMap) 26 | ); 27 | // -> myMap.equals(myModifiedMap) false 28 | console.log( 29 | 'myMap.equals(myUnmodifiedMap)', 30 | myMap.equals(myUnmodifiedMap) 31 | ); 32 | // -> myMap.equals(Map.of('first', 1)) true 33 | 34 | // Strict equality is much faster than using equals(), because 35 | // equals() has to do a lot more work. However, you can see 36 | // here that you have to be careful in cases where the 37 | // values are actually equal, yet strict equality returns 38 | // evaluates to false. 39 | console.log( 40 | 'myMap.equals(Map.of(\'first\', 1))', 41 | myMap.equals(Map.of('first', 1)) 42 | ); 43 | // -> myMap.equals(Map.of('first', 1)) true 44 | console.log( 45 | 'myMap === Map.of(\'first\', 1)', 46 | myMap === Map.of('first', 1) 47 | ); 48 | // -> myMap === Map.of('first', 1) false 49 | -------------------------------------------------------------------------------- /09/03-transformations-versus-mutations.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList = List.of( 4 | Map.of('one', 1, 'two', 2), 5 | Map.of('three', 3, 'four', 4), 6 | Map.of('five', 5, 'six', 6) 7 | ); 8 | 9 | // Transformation methods, like map(), never return the 10 | // same instance. Even in this case, where we're returning 11 | // the exact same map. 12 | const myTransformedList = myList.map(v => v); 13 | 14 | // Mutative methods, like update() and set(), will return 15 | // the same instance if nothing changes, as is the case here. 16 | const myMutatedList = myList 17 | .update(0, v => v.set('one', 1)); 18 | 19 | console.log('myList', myList.toJS()); 20 | // -> myList [ { one: 1, two: 2 }, 21 | // -> { three: 3, four: 4 }, 22 | // -> { five: 5, six: 6 } ] 23 | console.log('myTransformedList', myTransformedList.toJS()); 24 | // -> myTransformedList [ { one: 1, two: 2 }, 25 | // -> { three: 3, four: 4 }, 26 | // -> { five: 5, six: 6 } ] 27 | console.log('myMutatedList', myMutatedList.toJS()); 28 | // -> myMutatedList [ { one: 1, two: 2 }, 29 | // -> { three: 3, four: 4 }, 30 | // -> { five: 5, six: 6 } ] 31 | console.log( 32 | 'myList === myTransformedList', 33 | myList === myTransformedList 34 | ); 35 | // -> myList === myTransformedList false 36 | console.log( 37 | 'myList === myMutatedList', 38 | myList === myMutatedList 39 | ); 40 | // -> myList === myMutatedList true 41 | console.log( 42 | 'myList.equals(myTransformedList)', 43 | myList.equals(myTransformedList) 44 | ); 45 | // -> myList.equals(myTransformedList) true 46 | console.log( 47 | 'myList.equals(myMutatedList)', 48 | myList.equals(myMutatedList) 49 | ); 50 | // -> myList.equals(myMutatedList) true 51 | -------------------------------------------------------------------------------- /09/04-detecting-after-transfomations.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | 5 | // Transforms a list then iterates over it, logging 6 | // values. After we transform it, we check to see 7 | // if the result is the same as what we've 8 | // stored in prev. If the equals() method returns 9 | // true, then we can skip the side-effect. 10 | const mySideEffect = (list) => { 11 | const transformed = list 12 | .map(v => v * v); 13 | 14 | if (!transformed.equals(mySideEffect.prev)) { 15 | mySideEffect.prev = transformed; 16 | transformed.forEach( 17 | v => console.log('transformed', v) 18 | ); 19 | } 20 | }; 21 | 22 | console.log('first side-effect'); 23 | mySideEffect(myList); 24 | // -> first side-effect 25 | // -> transformed 1 26 | // -> transformed 4 27 | // -> transformed 9 28 | console.log('second side-effect'); 29 | mySideEffect(myList.set(0, 1)); 30 | // -> second side-effect 31 | console.log('third side-effect'); 32 | mySideEffect(myList.push(4)); 33 | // -> third side-effect 34 | // -> transformed 1 35 | // -> transformed 4 36 | // -> transformed 9 37 | // -> transformed 16 38 | console.log('fourth side-effect'); 39 | mySideEffect(myList.push(4)); 40 | // -> fourth side-effect 41 | -------------------------------------------------------------------------------- /09/05-detecting-before-transfomations.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3); 4 | const myModifiedList = myList.push(4); 5 | 6 | // Transforms a list then iterates over it, logging 7 | // values. After we transform it, we check to see 8 | // if the result is the same as what we've 9 | // stored in prev. If the equals() method returns 10 | // true, then we can skip the side-effect. 11 | const mySideEffect = (list) => { 12 | if (list !== mySideEffect.prev) { 13 | mySideEffect.prev = list; 14 | list 15 | .map(v => v * v) 16 | .forEach(v => console.log('transformed', v)); 17 | } 18 | }; 19 | 20 | console.log('first side-effect'); 21 | mySideEffect(myList); 22 | // -> first side-effect 23 | // -> transformed 1 24 | // -> transformed 4 25 | // -> transformed 9 26 | console.log('second side-effect'); 27 | mySideEffect(myList.set(0, 1)); 28 | // -> second side-effect 29 | console.log('third side-effect'); 30 | mySideEffect(myModifiedList); 31 | // -> third side-effect 32 | // -> transformed 1 33 | // -> transformed 4 34 | // -> transformed 9 35 | // -> transformed 16 36 | console.log('fourth side-effect'); 37 | mySideEffect(myModifiedList.set(0, 1)); 38 | // -> fourth side-effect 39 | -------------------------------------------------------------------------------- /09/06-caching-function-arguments.js: -------------------------------------------------------------------------------- 1 | import { List, Seq } from 'immutable'; 2 | 3 | // This is where cached argument values go. It's a WeakMap 4 | // because the functions that implement side effects are 5 | // the keys. When the function is garbage collected, we 6 | // want it's cache garbage collected too. 7 | const sideEffectCache = new WeakMap(); 8 | 9 | // Takes a function fn() and returns a new function that 10 | // caches argument values. When the function is called 11 | // again with the same argument values, nothing happens. 12 | // The idea is that nothing needs to happen since the 13 | // function implements side-effects. 14 | const sideEffect = fn => (...args) => { 15 | // Does the function have cached arguments? If not, 16 | // we'll initialize to an empty array so that we can 17 | // still zip() it with the args array. 18 | const cache = sideEffectCache.get(fn) 19 | || new Array(args.length); 20 | 21 | // This is true if no cached argument values are found. 22 | // Since the expectation is that each argument is an 23 | // Immutable.js type, we know that we can do a strict 24 | // comparison. So unless every argument value passes 25 | // the some() test, we consider it a miss and call 26 | // the side-effect. We also update the cached args. 27 | const miss = Seq(args) 28 | .zip(cache) 29 | .some(([a, b]) => a !== b); 30 | 31 | if (miss) { 32 | sideEffectCache.set(fn, args); 33 | fn(...args); 34 | } 35 | }; 36 | 37 | // We create side-effects by wrapping functions in sideEffect(). 38 | // Side effects are behavior that iterate over collections and 39 | // perform IO. 40 | const renderOddNumbers = sideEffect( 41 | coll => coll 42 | .toSeq() 43 | .filterNot(v => v % 2) 44 | .take(5) 45 | .forEach(v => console.log('odd', v)) 46 | ); 47 | 48 | // The same idea as renderOddNumbers(), except with two 49 | // parameters and a different side-effect. You so if we 50 | // get a cache hit, none of this code is executed - zip(), 51 | // map(), take(), and forEach(). If you're dealing with 52 | // a large collection, this is a big deal. 53 | const renderMultiples = sideEffect( 54 | (a, b) => a 55 | .toSeq() 56 | .zip(b) 57 | .map(([v1, v2]) => v1 * v2) 58 | .take(5) 59 | .forEach(v => console.log('multiple', v)) 60 | ); 61 | 62 | const myFirstList = List.of( 63 | 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 64 | 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 65 | ); 66 | 67 | const mySecondList = List.of( 68 | 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 69 | 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 70 | ); 71 | 72 | // Renders as expected, also populates the cache 73 | // for these two functions. 74 | console.log('first render'); 75 | renderOddNumbers(myFirstList); 76 | renderMultiples(myFirstList, mySecondList); 77 | // -> first render 78 | // -> odd 2 79 | // -> odd 4 80 | // -> odd 6 81 | // -> odd 8 82 | // -> odd 10 83 | // -> multiple 21 84 | // -> multiple 44 85 | // -> multiple 69 86 | // -> multiple 96 87 | // -> multiple 125 88 | 89 | // Looks like we're passing in different argument 90 | // values here, but, since nothing is actually changed 91 | // (index 0 is already 1), it's the same instance. 92 | // This means that nothing is rendered, because it's 93 | // already been rendered! 94 | console.log('second render'); 95 | renderOddNumbers(myFirstList.set(0, 1)); 96 | renderMultiples(myFirstList, mySecondList.set(0, 21)); 97 | // -> second render 98 | 99 | // This time we really are passing in different values. 100 | // The cache for these two functions is invalidated, and, 101 | // the side-effect is executed as expected. 102 | console.log('third render'); 103 | renderOddNumbers(myFirstList.insert(0, -1)); 104 | renderMultiples( 105 | myFirstList, 106 | mySecondList.push(100) 107 | ); 108 | // -> third render 109 | // -> odd 2 110 | // -> odd 4 111 | // -> odd 6 112 | // -> odd 8 113 | // -> odd 10 114 | // -> multiple 21 115 | // -> multiple 44 116 | // -> multiple 69 117 | // -> multiple 96 118 | // -> multiple 125 119 | -------------------------------------------------------------------------------- /10/01-not-the-same-as-lists.js: -------------------------------------------------------------------------------- 1 | import { List, Set } from 'immutable'; 2 | 3 | // In most ways, lists and maps are the same. 4 | const myList = List.of(1, 2, 3); 5 | const mySet = Set.of(1, 2, 3); 6 | 7 | console.log('myList', myList.toJS()); 8 | // -> myList [ 1, 2, 3 ] 9 | console.log('mySet', mySet.toJS()); 10 | // -> mySet [ 1, 2, 3 ] 11 | 12 | // Using get() with sets as though they're indexed 13 | // collections doesn't work. 14 | console.log('myList.get(0)', myList.get(0)); 15 | // -> myList.get(0) 1 16 | console.log('mySet.get(0)', mySet.get(0)); 17 | // -> mySet.get(0) undefined 18 | 19 | // Set iteration doesn't have a defined order, 20 | // so iterating over sets could have unexpected 21 | // results. 22 | console.log('myList.forEach()'); 23 | myList.forEach(v => console.log(v)); 24 | // -> myList.forEach() 25 | // -> 1 26 | // -> 2 27 | // -> 3 28 | console.log('mySet.forEach()'); 29 | mySet.forEach(v => console.log(v)); 30 | // -> mySet.forEach() 31 | // -> 1 32 | // -> 2 33 | // -> 3 34 | -------------------------------------------------------------------------------- /10/02-lists-to-sets-and-back.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | // A list with duplicates. 4 | const myList = List.of(1, 1, 2, 2, 3, 3); 5 | const mySet = myList.toSet(); 6 | 7 | // Converting a list to a set will remove duplicates. 8 | // You can then convert the list back to a list if needed. 9 | const myUniqueList = myList 10 | .toSet() 11 | .toList(); 12 | 13 | console.log('myList', myList.toJS()); 14 | // -> myList [ 1, 1, 2, 2, 3, 3 ] 15 | console.log('mySet', mySet.toJS()); 16 | // -> mySet [ 1, 2, 3 ] 17 | console.log('myUniqueList', myUniqueList.toJS()); 18 | // -> myUniqueList [ 1, 2, 3 ] 19 | -------------------------------------------------------------------------------- /10/03-lazy-duplicate-removal.js: -------------------------------------------------------------------------------- 1 | import { List, Map, Set, Repeat } from 'immutable'; 2 | 3 | const myList = List.of( 4 | Map.of('one', 1, 'two', 2), 5 | Map.of('three', 3, 'four', 4), 6 | Map.of('one', 1, 'two', 2), 7 | Map.of('five', 5, 'six', 6), 8 | Map.of('one', 1, 'two', 2) 9 | ); 10 | 11 | // This doesn't work, because there's no way to lazy 12 | // evaluate duplicates out of a list. 13 | myList 14 | .toSetSeq() 15 | .map(v => v.toJS()) 16 | .forEach(v => console.log('toSetSeq()', v)); 17 | // -> toSetSeq() { one: 1, two: 2 } 18 | // -> toSetSeq() { three: 3, four: 4 } 19 | // -> toSetSeq() { one: 1, two: 2 } 20 | // -> toSetSeq() { five: 5, six: 6 } 21 | // -> toSetSeq() { one: 1, two: 2 } 22 | 23 | 24 | // This works as expected, no more duplicates. 25 | myList 26 | .toSet() 27 | .toSeq() 28 | .map(v => v.toJS()) 29 | .forEach(v => console.log('toSet()', v)); 30 | // -> toSet() { one: 1, two: 2 } 31 | // -> toSet() { three: 3, four: 4 } 32 | // -> toSet() { five: 5, six: 6 } 33 | 34 | // We can lazily remove duplicates from lists 35 | // by keeping track of the values that have 36 | // already made it through. If we see it 37 | // again, we can ignore it. 38 | const lazySet = (list) => { 39 | let seen = Set(); 40 | 41 | return list 42 | .toSeq() 43 | .filter((v) => { 44 | if (seen.includes(v)) { 45 | return false; 46 | } 47 | 48 | seen = seen.add(v); 49 | return true; 50 | }); 51 | }; 52 | 53 | const myBigList = myList.concat( 54 | Repeat(Map.of('one', 1, 'two', 2), 10000) 55 | ); 56 | 57 | console.log('myBigList.size', myBigList.size); 58 | // -> myBigList.size 10005 59 | lazySet(myBigList) 60 | .map(v => v.toJS()) 61 | .take(2) 62 | .forEach(v => console.log('lazySet()', v)); 63 | // -> lazySet() { one: 1, two: 2 } 64 | // -> lazySet() { three: 3, four: 4 } 65 | -------------------------------------------------------------------------------- /10/04-sorting-ordered-sets.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of( 4 | 1, 3, 1, 2, 3, 4, 1, 5, 5 | 2, 4, 6, 1, 5, 2, 7, 1, 6 | 8, 3, 7, 1, 4, 2, 8, 9 7 | ); 8 | const myUniqueList = myList 9 | .toOrderedSet() 10 | .sort() 11 | .reverse() 12 | .toList(); 13 | 14 | console.log('myList', myList.toJS()); 15 | // -> myList [ 1, 3, 1, 2, 3, 4, 1, 5, 2, 4, 6, 16 | // -> 1, 5, 2, 7, 1, 8, 3, 7, 1, 4, 2, 17 | // -> 8, 9 ] 18 | console.log('myUniqueList', myUniqueList.toJS()); 19 | // -> myUniqueList [ 9, 8, 7, 6, 5, 20 | // -> 4, 3, 2, 1 ] 21 | -------------------------------------------------------------------------------- /10/05-iterating-ordered-sets.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of( 4 | 1, 3, 1, 2, 3, 4, 1, 5, 5 | 2, 4, 6, 1, 5, 2, 7, 1, 6 | 8, 3, 7, 1, 4, 2, 8, 9 7 | ); 8 | const myOrderedSet = myList 9 | .toOrderedSet() 10 | .sort() 11 | .reverse(); 12 | 13 | console.log('myList', myList.toJS()); 14 | // -> myList [ 1, 3, 1, 2, 3, 4, 1, 5, 2, 4, 6, 15 | // -> 1, 5, 2, 7, 1, 8, 3, 7, 1, 4, 2, 16 | // -> 8, 9 ] 17 | myOrderedSet.forEach(v => console.log('myOrderedSet', v)); 18 | // -> myOrderedSet 9 19 | // -> myOrderedSet 8 20 | // -> myOrderedSet 7 21 | // -> myOrderedSet 6 22 | // -> myOrderedSet 5 23 | // -> myOrderedSet 4 24 | // -> myOrderedSet 3 25 | // -> myOrderedSet 2 26 | // -> myOrderedSet 1 27 | -------------------------------------------------------------------------------- /10/06-adding-duplicates-to-sets.js: -------------------------------------------------------------------------------- 1 | import { OrderedSet, Map } from 'immutable'; 2 | 3 | // Prints the values of a set. The last property holds 4 | // the last set that was passed to this function. Since 5 | // adding a duplicate will always return a new set, even 6 | // though nothing changed, we have to use the more expensive 7 | // is() function to test for equality. 8 | const printValues = (set) => { 9 | if (!set.equals(printValues.prev)) { 10 | printValues.prev = set; 11 | set 12 | .valueSeq() 13 | .map(v => v.toJS()) 14 | .forEach(v => console.log(v)); 15 | } 16 | }; 17 | 18 | const myOrderedSet = OrderedSet.of( 19 | Map.of('one', 1), 20 | Map.of('two', 2) 21 | ); 22 | 23 | console.log('myOrderedSet'); 24 | printValues(myOrderedSet); 25 | // -> myOrderedSet 26 | // -> { one: 1 } 27 | // -> { two: 2 } 28 | 29 | console.log('adding 3'); 30 | printValues(myOrderedSet.add(Map.of('three', 3))); 31 | // -> adding 3 32 | // -> { one: 1 } 33 | // -> { two: 2 } 34 | // -> { three: 3 } 35 | 36 | console.log('adding 3 again'); 37 | printValues(myOrderedSet.add(Map.of('three', 3))); 38 | // -> adding 3 again 39 | -------------------------------------------------------------------------------- /11/01-set-intersections.js: -------------------------------------------------------------------------------- 1 | import { Set } from 'immutable'; 2 | 3 | const myFirstSet = Set.of(1, 2, 3, 4, 5, 6); 4 | const mySecondSet = Set.of(2, 4, 6, 8, 10); 5 | 6 | // Create a new set by intersecting with another. 7 | const myIntersection = myFirstSet.intersect(mySecondSet); 8 | 9 | // The resulting set intersection will always 10 | // be in the correct order since we're converting 11 | // it to an ordered set before intersecting. 12 | const myOrderedIntersection = myFirstSet 13 | .sort() 14 | .intersect(mySecondSet); 15 | 16 | console.log('myFirstSet', myFirstSet.toJS()); 17 | // -> myFirstSet [ 1, 2, 3, 4, 5, 6 ] 18 | console.log('mySecondSet', mySecondSet.toJS()); 19 | // -> mySecondSet [ 2, 4, 6, 8, 10 ] 20 | console.log('myIntersection', myIntersection.toJS()); 21 | // -> myIntersection [ 6, 2, 4 ] 22 | console.log('myOrderedIntersection', myOrderedIntersection.toJS()); 23 | // -> myOrderedIntersection [ 2, 4, 6 ] 24 | -------------------------------------------------------------------------------- /11/02-list-intersections.js: -------------------------------------------------------------------------------- 1 | import { List, Seq, Set } from 'immutable'; 2 | 3 | // Intersects the given lists, resulting in a new 4 | // list. The idea is to reduce the first list to 5 | // a new list that includes values that exist 6 | // in every other list. 7 | const intersect = (...lists) => { 8 | const [head] = lists; 9 | const tail = Seq(lists.slice(1)); 10 | 11 | return head.reduce((result, value) => 12 | tail 13 | .map(list => list.includes(value)) 14 | .includes(false) ? 15 | result : result.add(value), 16 | Set() 17 | ); 18 | }; 19 | 20 | const myList1 = List.of(1, 2, 3, 2); 21 | const myList2 = List.of(2, 3, 4); 22 | const myList3 = List.of(2, 3, 5); 23 | 24 | const myIntersection = intersect( 25 | myList1, 26 | myList2, 27 | myList3 28 | ); 29 | 30 | console.log('myList1', myList1.toJS()); 31 | // -> myList1 [ 1, 2, 3, 2 ] 32 | console.log('myList2', myList2.toJS()); 33 | // -> myList2 [ 2, 3, 4 ] 34 | console.log('myList3', myList3.toJS()); 35 | // -> myList3 [ 2, 3, 5 ] 36 | console.log('myIntersection', myIntersection.toJS()); 37 | // -> myIntersection [ 2, 3 ] 38 | -------------------------------------------------------------------------------- /11/03-set-differences.js: -------------------------------------------------------------------------------- 1 | import { Set } from 'immutable'; 2 | 3 | const mySet1 = Set.of('first', 'second', 'third'); 4 | const mySet2 = Set.of('first', 'second'); 5 | 6 | // You're taking whatever is in mySet2 out of 7 | // mySet1. Since "third" isn't in mySet2, it's 8 | // the difference. 9 | const myDiff1 = mySet1.subtract(mySet2); 10 | 11 | // Here, the order of the operation is changed, and 12 | // there's nothing left over after subtracting mySet1 13 | // from mySet2. This can be misleading since there's 14 | // obviously a difference between the two lists. 15 | const myDiff2 = mySet2.subtract(mySet1); 16 | 17 | console.log('mySet1', mySet1.toJS()); 18 | // -> mySet1 [ 'first', 'second', 'third' ] 19 | console.log('mySet2', mySet2.toJS()); 20 | // -> mySet2 [ 'first', 'second' ] 21 | console.log('myDiff1', myDiff1.toJS()); 22 | // -> myDiff1 [ 'third' ] 23 | console.log('myDiff2', myDiff2.toJS()); 24 | // -> myDiff2 [] 25 | -------------------------------------------------------------------------------- /11/04-list-differences.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // Takes an arbitrary number of lists, and finds the 4 | // difference between them. 5 | const difference = (...lists) => 6 | List() 7 | .concat(...lists) 8 | .countBy(v => v) 9 | .toSeq() 10 | .filter(v => v < lists.length) 11 | .keySeq(); 12 | 13 | // Works just like difference(), except it uses a different 14 | // filter(). 15 | const intersection = (...lists) => 16 | List() 17 | .concat(...lists) 18 | .countBy(v => v) 19 | .toSeq() 20 | .filter(v => v === lists.length) 21 | .keySeq(); 22 | 23 | const myList1 = List.of( 24 | Map.of('first', 1, 'second', 2), 25 | Map.of('third', 3, 'fourth', 4) 26 | ); 27 | 28 | const myList2 = List.of( 29 | Map.of('third', 3, 'fourth', 4), 30 | Map.of('fifth', 5, 'sixth', 6) 31 | ); 32 | 33 | const myList3 = List.of( 34 | Map.of('first', 1, 'second', 2), 35 | Map.of('third', 3, 'fourth', 4), 36 | Map.of('seventh', 7, 'eighth', 8) 37 | ); 38 | 39 | console.log('myList1', myList1.toJS()); 40 | // -> myList1 [ { first: 1, second: 2 }, { third: 3, fourth: 4 } ] 41 | console.log('myList2', myList2.toJS()); 42 | // -> myList2 [ { third: 3, fourth: 4 }, { fifth: 5, sixth: 6 } ] 43 | console.log('myList3', myList3.toJS()); 44 | // -> myList3 [ { first: 1, second: 2 }, 45 | // -> { third: 3, fourth: 4 }, 46 | // -> { seventh: 7, eighth: 8 } ] 47 | 48 | difference(myList1, myList2, myList3) 49 | .forEach((v) => { 50 | console.log('first diff', v.toJS()); 51 | }); 52 | // -> first diff { first: 1, second: 2 } 53 | // -> first diff { fifth: 5, sixth: 6 } 54 | // -> first diff { seventh: 7, eighth: 8 } 55 | 56 | difference(myList3, myList1, myList2) 57 | .forEach((v) => { 58 | console.log('second diff', v.toJS()); 59 | }); 60 | // -> second diff { first: 1, second: 2 } 61 | // -> second diff { seventh: 7, eighth: 8 } 62 | // -> second diff { fifth: 5, sixth: 6 } 63 | 64 | intersection(myList1, myList2, myList3) 65 | .forEach((v) => { 66 | console.log('intersection', v.toJS()); 67 | }); 68 | // -> intersection { third: 3, fourth: 4 } 69 | -------------------------------------------------------------------------------- /11/05-map-key-differences.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | // This works like diffing lists except that we have 4 | // to convert the maps into a list of key-value pair 5 | // lists. Then we can do our usual filter, then 6 | // convert them to a map. The end result is 7 | // a map with the key value pairs that aren't 8 | // in every map. 9 | const difference = (...maps) => 10 | Map(List() 11 | .concat(...maps.map(m => m.entrySeq())) 12 | .map(List) 13 | .countBy(v => v) 14 | .filter(v => v < maps.length) 15 | .keySeq()); 16 | 17 | // Intersection is the same as diffing except that 18 | // we want key-value pairs that exist in every map 19 | // argument. 20 | const intersection = (...maps) => 21 | Map(List() 22 | .concat(...maps.map(m => m.entrySeq())) 23 | .map(List) 24 | .countBy(v => v) 25 | .toSeq() 26 | .filter(v => v === maps.length) 27 | .keySeq()); 28 | 29 | const myMap1 = Map.of( 30 | 'one', 1, 'two', 2, 'three', 3 31 | ); 32 | 33 | const myMap2 = Map.of( 34 | 'one', 1, 'three', 3, 'four', 4 35 | ); 36 | 37 | const myMap3 = Map.of( 38 | 'one', 1, 'two', 2, 'five', 5 39 | ); 40 | 41 | console.log('difference'); 42 | difference(myMap1, myMap2, myMap3) 43 | .forEach((v, k) => console.log(k, v)); 44 | // -> difference 45 | // -> two 2 46 | // -> three 3 47 | // -> four 4 48 | // -> five 5 49 | 50 | console.log('intersection'); 51 | intersection(myMap1, myMap2, myMap3) 52 | .forEach((v, k) => console.log(k, v)); 53 | // -> intersection 54 | // -> one 1 55 | -------------------------------------------------------------------------------- /11/06-list-subsets.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of( 4 | List.of(1, 2, 3), 5 | List.of(4, 5, 6), 6 | List.of(7, 8, 9) 7 | ); 8 | 9 | const isSubset = List.of(1, 4, 7) 10 | .isSubset(myList.flatten()); 11 | 12 | const isSuperset = myList 13 | .flatten() 14 | .isSuperset(List.of(2, 5, 8)); 15 | 16 | console.log('isSubset', isSubset); 17 | // -> isSubset true 18 | console.log('isSuperset', isSuperset); 19 | // -> isSuperset true 20 | -------------------------------------------------------------------------------- /12/01-merging-maps-by-key.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myMap1 = Map.of( 4 | 'one', 1, 'two', 2, 'three', 3 5 | ); 6 | const myMap2 = Map.of( 7 | 'two', 22, 'three', 33, 'four', 4 8 | ); 9 | const myMergedMap = myMap1.merge(myMap2); 10 | const myMergedWithMap = myMap1.mergeWith( 11 | v => v, 12 | myMap2 13 | ); 14 | 15 | console.log('myMap1', myMap1.toJS()); 16 | // -> myMap1 { one: 1, two: 2, three: 3 } 17 | console.log('myMap2', myMap2.toJS()); 18 | // -> myMap2 { two: 22, three: 33, four: 4 } 19 | console.log('myMergedMap', myMergedMap.toJS()); 20 | // -> myMergedMap { one: 1, two: 22, three: 33, four: 4 } 21 | console.log('myMergedWithMap', myMergedWithMap.toJS()); 22 | // -> myMergedWithMap { one: 1, two: 2, three: 3, four: 4 } 23 | -------------------------------------------------------------------------------- /12/02-merging-maps-with-complex-keys.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myMap1 = Map.of( 4 | Map.of('name', 'one'), 1, 5 | Map.of('name', 'two'), 2, 6 | Map.of('name', 'three'), 3 7 | ); 8 | const myMap2 = Map.of( 9 | Map.of('name', 'two'), 22, 10 | Map.of('name', 'three'), 33, 11 | Map.of('name', 'four'), 4 12 | ); 13 | const myMergedMap = myMap1.merge(myMap2); 14 | const myMergedWith = myMap1.mergeWith( 15 | v => v, 16 | myMap2 17 | ); 18 | 19 | console.log('myMap1', myMap1.toJS()); 20 | // -> myMap1 { 'Map { "name": "one" }': 1, 21 | // -> 'Map { "name": "two" }': 2, 22 | // -> 'Map { "name": "three" }': 3 } 23 | console.log('myMap2', myMap2.toJS()); 24 | // -> myMap2 { 'Map { "name": "two" }': 22, 25 | // -> 'Map { "name": "three" }': 33, 26 | // -> 'Map { "name": "four" }': 4 } 27 | console.log('myMergedMap', myMergedMap.toJS()); 28 | // -> myMergedMap { 'Map { "name": "one" }': 1, 29 | // -> 'Map { "name": "two" }': 22, 30 | // -> 'Map { "name": "three" }': 33, 31 | // -> 'Map { "name": "four" }': 4 } 32 | console.log('myMergedWith', myMergedWith.toJS()); 33 | // -> myMergedWith { 'Map { "name": "one" }': 1, 34 | // -> 'Map { "name": "two" }': 2, 35 | // -> 'Map { "name": "three" }': 3, 36 | // -> 'Map { "name": "four" }': 4 } 37 | -------------------------------------------------------------------------------- /12/03-merging-lists-of-simple-values.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList1 = List.of(1, 2, 3); 4 | const myList2 = List.of(2, 3, 4, 5); 5 | const myMergedList = myList1.merge(myList2); 6 | const myMergedWithList = myList1.mergeWith( 7 | (a, b) => a === undefined ? b : a, 8 | myList2 9 | ); 10 | const myMergedReversed = myList2.mergeWith( 11 | (a, b) => a === undefined ? b : a, 12 | myList1 13 | ); 14 | 15 | 16 | console.log('myList1', myList1.toJS()); 17 | // -> myList1 [ 1, 2, 3 ] 18 | console.log('myList2', myList2.toJS()); 19 | // -> myList2 [ 2, 3, 4, 5 ] 20 | console.log('myMergedList', myMergedList.toJS()); 21 | // -> myMergedList [ 2, 3, 4, 5 ] 22 | console.log('myMergedWithList', myMergedWithList.toJS()); 23 | // -> myMergedWithList [ 1, 2, 3, 5 ] 24 | console.log('myMergedReversed', myMergedReversed.toJS()); 25 | // -> myMergedReversed [ 2, 3, 4, 5 ] 26 | -------------------------------------------------------------------------------- /12/04-merging-lists-of-maps.js: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable'; 2 | 3 | const myList1 = List.of( 4 | Map.of('one', 1, 'two', 2), 5 | Map.of('three', 3, 'four', 4) 6 | ); 7 | const myList2 = List.of( 8 | Map.of('one', 11, 'two', 22, 'three', 3), 9 | Map.of('four', 44, 'five', 5, 'size', 6) 10 | ); 11 | const myMergedList = myList1.merge(myList2); 12 | const myMergedWithList = myList1.mergeWith( 13 | (a, b) => a.mergeWith(v => v, b), 14 | myList2 15 | ); 16 | 17 | console.log('myList1', myList1.toJS()); 18 | // -> myList1 [ { one: 1, two: 2 }, { three: 3, four: 4 } ] 19 | console.log('myList2', myList2.toJS()); 20 | // -> myList2 [ { one: 11, two: 22, three: 3 }, 21 | // -> { four: 44, five: 5, size: 6 } ] 22 | console.log('myMergedList', myMergedList.toJS()); 23 | // -> myMergedList [ { one: 11, two: 22, three: 3 }, 24 | // -> { four: 44, five: 5, size: 6 } ] 25 | console.log('myMergedWithList', myMergedWithList.toJS()); 26 | // -> myMergedWithList [ { one: 1, two: 2, three: 3 }, 27 | // -> { three: 3, four: 4, five: 5, size: 6 } ] 28 | -------------------------------------------------------------------------------- /12/05-merging-lists-of-lists.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList1 = List.of( 4 | List.of(1, 2), 5 | List.of(3, 4) 6 | ); 7 | const myList2 = List.of( 8 | List.of(11, 21, 3), 9 | List.of(33, 44, 5) 10 | ); 11 | const myMergedList = myList1.merge(myList2); 12 | const myMergedWithList = myList1.mergeWith( 13 | (a, b) => a.mergeWith((a1, b1) => a1 === undefined ? b1 : a1, b), 14 | myList2 15 | ); 16 | 17 | console.log('myList1', myList1.toJS()); 18 | // -> myList1 [ [ 1, 2 ], [ 3, 4 ] ] 19 | console.log('myList2', myList2.toJS()); 20 | // -> myList2 [ [ 11, 21, 3 ], [ 33, 44, 5 ] ] 21 | console.log('myMergedList', myMergedList.toJS()); 22 | // -> myMergedList [ [ 11, 21, 3 ], [ 33, 44, 5 ] ] 23 | console.log('myMergedWithList', myMergedWithList.toJS()); 24 | // -> myMergedWithList [ [ 1, 2, 3 ], [ 3, 4, 5 ] ] 25 | -------------------------------------------------------------------------------- /12/06-concatenating-list-values.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList1 = List.of(1, 2, 3); 4 | const myList2 = List.of(4, 5, 6); 5 | const myList3 = List.of(7, 8, 9); 6 | const myCombinedList1 = myList1.concat( 7 | myList2, 8 | myList3 9 | ); 10 | 11 | console.log('myList1', myList1.toJS()); 12 | // -> myList1 [ 1, 2, 3 ] 13 | console.log('myList2', myList2.toJS()); 14 | // -> myList2 [ 4, 5, 6 ] 15 | console.log('myList3', myList3.toJS()); 16 | // -> myList3 [ 7, 8, 9 ] 17 | console.log('myCombinedList1', myCombinedList1.toJS()); 18 | // -> myCombinedList1 [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ] 19 | -------------------------------------------------------------------------------- /12/07-lazy-concatenating-sequences.js: -------------------------------------------------------------------------------- 1 | import { Seq, Range } from 'immutable'; 2 | 3 | const mySeq1 = Range(1, 6) 4 | .filterNot((v) => { 5 | console.log('mySeq1', v); 6 | return v % 2; 7 | }); 8 | const mySeq2 = Range(6, 11) 9 | .filterNot((v) => { 10 | console.log('mySeq2', v); 11 | return v % 2; 12 | }); 13 | 14 | Seq() 15 | .concat(mySeq1, mySeq2) 16 | .forEach(v => console.log('result', v)); 17 | // -> mySeq1 1 18 | // -> mySeq1 2 19 | // -> result 2 20 | // -> mySeq1 3 21 | // -> mySeq1 4 22 | // -> result 4 23 | // -> mySeq1 5 24 | // -> mySeq2 6 25 | // -> result 6 26 | // -> mySeq2 7 27 | // -> mySeq2 8 28 | // -> result 8 29 | // -> mySeq2 9 30 | // -> mySeq2 10 31 | // -> result 10 32 | -------------------------------------------------------------------------------- /12/08-interposing-values.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList = List.of(1, 2, 3, 4, 5, 6, 7, 8); 4 | 5 | myList 6 | .toSeq() 7 | .filter((v) => { 8 | console.log('filtering', v); 9 | return v % 2; 10 | }) 11 | .interpose('...') 12 | .forEach(v => console.log(v)); 13 | // -> filtering 1 14 | // -> 1 15 | // -> filtering 2 16 | // -> filtering 3 17 | // -> ... 18 | // -> 3 19 | // -> filtering 4 20 | // -> filtering 5 21 | // -> ... 22 | // -> 5 23 | // -> filtering 6 24 | // -> filtering 7 25 | // -> ... 26 | // -> 7 27 | // -> filtering 8 28 | -------------------------------------------------------------------------------- /12/09-interleaving-values.js: -------------------------------------------------------------------------------- 1 | import { List } from 'immutable'; 2 | 3 | const myList1 = List.of(1, 2, 3); 4 | const myList2 = List.of(4, 5, 6); 5 | const myList3 = List.of(7, 8, 9); 6 | 7 | console.log('found', myList1 8 | .toSeq() 9 | .interleave(myList2, myList3) 10 | .find((v) => { 11 | console.log('checking', v); 12 | return v === 7; 13 | })); 14 | // -> checking 1 15 | // -> checking 4 16 | // -> checking 7 17 | // -> found 7 18 | -------------------------------------------------------------------------------- /13/01-maps-of-behavior.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const actions = Map.of( 4 | 'first', () => console.log('first'), 5 | 'second', () => console.log('second'), 6 | 'third', () => console.log('third') 7 | ); 8 | 9 | actions.get('second')(); 10 | // -> second 11 | actions.get('third')(); 12 | // -> third 13 | -------------------------------------------------------------------------------- /13/02-wrapping-behavior-maps-in-functions.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myBehavior = action => Map.of( 4 | 'first', () => console.log('first'), 5 | 'second', () => console.log('second'), 6 | 'third', () => console.log('third') 7 | ).get(action)(); 8 | 9 | myBehavior('first'); 10 | // -> first 11 | myBehavior('second'); 12 | // -> second 13 | myBehavior(); 14 | // -> TypeError: Map.of(...).get(...) is not a function 15 | -------------------------------------------------------------------------------- /13/03-providing-default-behavior.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const myBehavior = action => Map.of( 4 | true, () => console.log('thruth'), 5 | false, () => console.log('lies') 6 | ).get( 7 | action, 8 | () => console.log('default') 9 | )(); 10 | 11 | myBehavior(true); 12 | // -> truth 13 | myBehavior(false); 14 | // -> lies 15 | myBehavior(); 16 | // -> default 17 | myBehavior(123); 18 | // -> default 19 | -------------------------------------------------------------------------------- /13/04-parameterizing-behavior.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const log = value => console.log('log:', value); 4 | 5 | const myBehavior = (action, ...args) => Map.of( 6 | 'start', value => log(`starting ${value}...`), 7 | 'stop', value => log(`stopping ${value}...`) 8 | ).get( 9 | action, 10 | () => {} 11 | )(...args); 12 | 13 | myBehavior('start', 'database'); 14 | // -> log: starting database... 15 | myBehavior('start', 'server'); 16 | // -> log: starting server... 17 | myBehavior('stop', 'database'); 18 | // -> log: stopping database... 19 | myBehavior('stop', 'server'); 20 | // -> log: stopping server... 21 | -------------------------------------------------------------------------------- /13/05-composing-behavior.js: -------------------------------------------------------------------------------- 1 | import { Map } from 'immutable'; 2 | 3 | const behavior = (behaviors, defaultBehavior = () => {}) => 4 | (action, ...args) => 5 | behaviors.get(action, defaultBehavior)(...args); 6 | 7 | const greaterThanFive = behavior(Map.of( 8 | true, n => `${n} is greater than 5`, 9 | false, n => `${n} is less than 5` 10 | )); 11 | 12 | const lessThanTen = behavior(Map.of( 13 | true, n => `${n} is less than 10`, 14 | false, n => `${n} is greater than 10` 15 | )); 16 | 17 | const a = 6; 18 | const b = 1; 19 | const c = 12; 20 | 21 | console.log(greaterThanFive(a > 5, a)); 22 | // -> 6 is greater than 5 23 | console.log(lessThanTen(a < 10, a)); 24 | // -> 6 is less than 10 25 | console.log(greaterThanFive(b > 5, b)); 26 | // -> 1 is less than 5 27 | console.log(lessThanTen(c < 10, c)); 28 | // -> 12 is greater than 10 29 | -------------------------------------------------------------------------------- /13/06-declarative-and-or-conditions.js: -------------------------------------------------------------------------------- 1 | import { Map, Seq } from 'immutable'; 2 | 3 | const behavior = (behaviors, defaultBehavior = () => {}) => 4 | (action, ...args) => 5 | behaviors.get(action, defaultBehavior)(...args); 6 | 7 | const some = (...predicates) => { 8 | const predicateSeq = Seq(predicates); 9 | return (...args) => predicateSeq.some(p => p(...args)); 10 | }; 11 | 12 | const every = (...predicates) => { 13 | const predicateSeq = Seq(predicates); 14 | return (...args) => predicateSeq.every(p => p(...args)); 15 | }; 16 | 17 | const hasEnough = some( 18 | v => v.get('source1') > 5, 19 | v => v.get('source2') > 5, 20 | v => v.get('source3') > 5 21 | ); 22 | 23 | const hasEverything = every( 24 | v => v.get('enabled'), 25 | v => v.get('hasPermission'), 26 | v => v.get('oldEnough') 27 | ); 28 | 29 | const hasBoth = every( 30 | hasEnough, 31 | hasEverything 32 | ); 33 | 34 | const logHasEnough = behavior(Map.of( 35 | true, () => console.log('yep, has enough'), 36 | false, () => console.log('sorry, not enough') 37 | )); 38 | 39 | const logHasEverything = behavior(Map.of( 40 | true, () => console.log('yep, has everything'), 41 | false, () => console.log('sorry, not everything') 42 | )); 43 | 44 | const logHasBoth = behavior(Map.of( 45 | true, () => console.log('yep, it has all of it'), 46 | false, () => console.log('nope') 47 | )); 48 | 49 | const myMap1 = Map.of( 50 | 'source1', 5, 51 | 'source2', 6, 52 | 'source3', 7 53 | ); 54 | const myMap2 = Map.of( 55 | 'source1', 6 56 | ); 57 | const myMap3 = Map.of( 58 | 'source1', 1, 59 | 'source2', 2, 60 | 'source3', 3 61 | ); 62 | const myMap4 = Map.of( 63 | 'enabled', true, 64 | 'hasPermission', true, 65 | 'oldEnough', true 66 | ); 67 | const myMap5 = Map.of( 68 | 'enabled', true, 69 | 'hasPermission', true 70 | ); 71 | const myMap6 = myMap1.merge(myMap4); 72 | const myMap7 = myMap1.merge(myMap5); 73 | 74 | console.log('myMap1', myMap1.toJS()); 75 | logHasEnough(hasEnough(myMap1)); 76 | // -> myMap1 { source1: 5, source2: 6, source3: 7 } 77 | // -> yep, has enough 78 | console.log('myMap2', myMap2.toJS()); 79 | logHasEnough(hasEnough(myMap2)); 80 | // -> myMap2 { source1: 6 } 81 | // -> yep, has enough 82 | console.log('myMap3', myMap3.toJS()); 83 | logHasEnough(hasEnough(myMap3)); 84 | // -> myMap3 { source1: 1, source2: 2, source3: 3 } 85 | // -> sorry, not enough 86 | console.log('myMap4', myMap4.toJS()); 87 | logHasEverything(hasEverything(myMap4)); 88 | // -> myMap4 { enabled: true, hasPermission: true, oldEnough: true } 89 | // -> yep, has everything 90 | console.log('myMap5', myMap5.toJS()); 91 | logHasEverything(hasEverything(myMap5)); 92 | // -> myMap5 { enabled: true, hasPermission: true } 93 | // -> sorry, not everything 94 | console.log('myMap6', myMap6.toJS()); 95 | logHasBoth(hasBoth(myMap6)); 96 | // -> myMap6 { source1: 5, source2: 6, 97 | // -> source3: 7, enabled: true, 98 | // -> hasPermission: true, oldEnough: true } 99 | // -> yep, it has all of it 100 | console.log('myMap7', myMap7.toJS()); 101 | logHasBoth(hasBoth(myMap7)); 102 | // -> myMap6 { source1: 5, source2: 6, 103 | // -> source3: 7, enabled: true, 104 | // -> hasPermission: true } 105 | // -> nope 106 | -------------------------------------------------------------------------------- /14/immutable-dom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Immutable.js + DOM Side Effects 6 | 7 | 8 | 9 | 10 | 11 | 12 | 24 |
25 |

Immutable.js + DOM Side Effects

26 | 27 |
28 |
29 | 65 |
66 |
67 |

Results

68 |
69 | 70 |
71 |
72 | 73 | 74 | -------------------------------------------------------------------------------- /14/immutable-dom.js: -------------------------------------------------------------------------------- 1 | const { 2 | List, 3 | Map, 4 | Seq 5 | } = Immutable; // eslint-disable-line no-undef 6 | 7 | const episodes = List.of( 8 | Map.of( 9 | 'title', 'The National Anthem', 10 | 'date', 'December 4 2011', 11 | 'director', 'Otto Bathurst', 12 | 'rating', 8.0 13 | ), 14 | Map.of( 15 | 'title', 'Fifteen Million Merits', 16 | 'date', 'December 11 2011', 17 | 'director', 'Euros Lyn', 18 | 'rating', 8.3 19 | ), 20 | Map.of( 21 | 'title', 'The Entire History of You', 22 | 'date', 'December 18 2011', 23 | 'director', 'Brian Welsh', 24 | 'rating', 8.7 25 | ), 26 | Map.of( 27 | 'title', 'Be Right Back', 28 | 'date', 'February 1 2013', 29 | 'director', 'Owen Harris', 30 | 'rating', 8.2 31 | ), 32 | Map.of( 33 | 'title', 'White Bear', 34 | 'date', 'February 18 2013', 35 | 'director', 'Carl Tibbetts', 36 | 'rating', 8.2 37 | ), 38 | Map.of( 39 | 'title', 'The Waldo Moment', 40 | 'date', 'February 25 2013', 41 | 'director', 'Bryn Higgins', 42 | 'rating', 7.0 43 | ), 44 | Map.of( 45 | 'title', 'White Christmas', 46 | 'date', 'December 16 2013', 47 | 'director', 'Carl Tibbetts', 48 | 'rating', 9.1 49 | ), 50 | Map.of( 51 | 'title', 'Nosedive', 52 | 'date', 'October 21 2016', 53 | 'director', 'Joe Wright', 54 | 'rating', 8.3 55 | ), 56 | Map.of( 57 | 'title', 'Playtest', 58 | 'date', 'October 21 2016', 59 | 'director', 'Dan Trachtenberg', 60 | 'rating', 8.2 61 | ), 62 | Map.of( 63 | 'title', 'Shut Up and Dance', 64 | 'date', 'October 21 2016', 65 | 'director', 'James Watkins', 66 | 'rating', 8.5 67 | ), 68 | Map.of( 69 | 'title', 'San Junipero', 70 | 'date', 'October 21 2016', 71 | 'director', 'Owen Harris', 72 | 'rating', 8.8 73 | ), 74 | Map.of( 75 | 'title', 'Men Against Fire', 76 | 'date', 'October 21 2016', 77 | 'director', 'Jakob Verbruggen', 78 | 'rating', 7.9 79 | ), 80 | Map.of( 81 | 'title', 'Hated in the Nation', 82 | 'date', 'October 21 2016', 83 | 'director', 'James Hawes', 84 | 'rating', 8.7 85 | ) 86 | ); 87 | 88 | const results = document.getElementById('results'); 89 | const title = document.querySelector('input[name="title"]'); 90 | const date = document.querySelector('input[name="date"]'); 91 | const director = document.querySelector('input[name="director"]'); 92 | const rating = document.querySelector('input[name="rating"]'); 93 | const search = document.querySelector('input[type="search"]'); 94 | const episodeTemplate = document.getElementById('episode-template'); 95 | 96 | const behavior = (behaviors, defaultBehavior = () => {}) => 97 | (action, ...args) => 98 | behaviors.get(action, defaultBehavior)(...args); 99 | 100 | const some = (...predicates) => { 101 | const predicateSeq = Seq(predicates); 102 | return (...args) => predicateSeq.some(p => p(...args)); 103 | }; 104 | 105 | const every = (...predicates) => { 106 | const predicateSeq = Seq(predicates); 107 | return (...args) => predicateSeq.every(p => p(...args)); 108 | }; 109 | 110 | const everyThen = (func, ...predicates) => 111 | (...args) => behavior(Map.of( 112 | true, func 113 | ))(every(...predicates)(...args), ...args); 114 | 115 | const filter = query => every( 116 | some( 117 | () => query === undefined, 118 | () => query === '', 119 | every( 120 | () => title.checked, 121 | v => v.get('title').includes(query) 122 | ), 123 | every( 124 | () => date.checked, 125 | v => v.get('date').includes(query) 126 | ), 127 | every( 128 | () => director.checked, 129 | v => v.get('director').includes(query) 130 | ) 131 | ), 132 | v => v.get('rating') >= (+rating.value) 133 | ); 134 | 135 | const render = (query) => { 136 | while (results.firstChild) { 137 | results.removeChild(results.firstChild); 138 | } 139 | 140 | episodes 141 | .filter(filter(query)) 142 | .forEach((v) => { 143 | const { content } = episodeTemplate; 144 | const episodeTitle = content.querySelector('h4'); 145 | const episodeDate = content.querySelector('p:nth-of-type(1)'); 146 | const episodeDirector = content.querySelector('p:nth-of-type(2)'); 147 | const episodeRating = content.querySelector('p:nth-of-type(3)'); 148 | 149 | episodeTitle.textContent = v.get('title'); 150 | episodeDate.childNodes[1].nodeValue = v.get('date'); 151 | episodeDirector.childNodes[1].nodeValue = v.get('director'); 152 | episodeRating.childNodes[1].nodeValue = v.get('rating'); 153 | results.appendChild(document.importNode(content, true)); 154 | }); 155 | }; 156 | 157 | document.addEventListener('input', everyThen( 158 | e => render(e.target.value), 159 | e => e.target instanceof HTMLInputElement, 160 | e => e.target.type === 'search' 161 | )); 162 | 163 | document.addEventListener('change', everyThen( 164 | () => render(search.value), 165 | e => e.target instanceof HTMLInputElement, 166 | e => [ 167 | 'title', 168 | 'date', 169 | 'director', 170 | 'rating' 171 | ].includes(e.target.name) 172 | )); 173 | 174 | document.addEventListener('change', everyThen( 175 | (e) => { 176 | e.target.parentNode.childNodes[0].nodeValue = e.target.value; 177 | }, 178 | e => e.target instanceof HTMLInputElement, 179 | e => e.target.name === 'rating' 180 | )); 181 | 182 | render(); 183 | -------------------------------------------------------------------------------- /14/immutable-react.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Immutable.js + React Side Effects 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /14/immutable-react.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 4 | 5 | /* eslint-disable react/prop-types */ 6 | /* eslint-disable jsx-a11y/label-has-for */ 7 | const { 8 | List, 9 | Map, 10 | Seq 11 | } = Immutable; // eslint-disable-line no-undef 12 | 13 | const React = window.React; 14 | 15 | const episodes = List.of(Map.of('title', 'The National Anthem', 'date', 'December 4 2011', 'director', 'Otto Bathurst', 'rating', 8.0), Map.of('title', 'Fifteen Million Merits', 'date', 'December 11 2011', 'director', 'Euros Lyn', 'rating', 8.3), Map.of('title', 'The Entire History of You', 'date', 'December 18 2011', 'director', 'Brian Welsh', 'rating', 8.7), Map.of('title', 'Be Right Back', 'date', 'February 1 2013', 'director', 'Owen Harris', 'rating', 8.2), Map.of('title', 'White Bear', 'date', 'February 18 2013', 'director', 'Carl Tibbetts', 'rating', 8.2), Map.of('title', 'The Waldo Moment', 'date', 'February 25 2013', 'director', 'Bryn Higgins', 'rating', 7.0), Map.of('title', 'White Christmas', 'date', 'December 16 2013', 'director', 'Carl Tibbetts', 'rating', 9.1), Map.of('title', 'Nosedive', 'date', 'October 21 2016', 'director', 'Joe Wright', 'rating', 8.3), Map.of('title', 'Playtest', 'date', 'October 21 2016', 'director', 'Dan Trachtenberg', 'rating', 8.2), Map.of('title', 'Shut Up and Dance', 'date', 'October 21 2016', 'director', 'James Watkins', 'rating', 8.5), Map.of('title', 'San Junipero', 'date', 'October 21 2016', 'director', 'Owen Harris', 'rating', 8.8), Map.of('title', 'Men Against Fire', 'date', 'October 21 2016', 'director', 'Jakob Verbruggen', 'rating', 7.9), Map.of('title', 'Hated in the Nation', 'date', 'October 21 2016', 'director', 'James Hawes', 'rating', 8.7)); 16 | 17 | /*const results = document.getElementById('results'); 18 | const title = document.querySelector('input[name="title"]'); 19 | const date = document.querySelector('input[name="date"]'); 20 | const director = document.querySelector('input[name="director"]'); 21 | const rating = document.querySelector('input[name="rating"]'); 22 | const search = document.querySelector('input[type="search"]'); 23 | const episodeTemplate = document.getElementById('episode-template');*/ 24 | 25 | const behavior = (behaviors, defaultBehavior = () => {}) => (action, ...args) => behaviors.get(action, defaultBehavior)(...args); 26 | 27 | const some = (...predicates) => { 28 | const predicateSeq = Seq(predicates); 29 | return (...args) => predicateSeq.some(p => p(...args)); 30 | }; 31 | 32 | const every = (...predicates) => { 33 | const predicateSeq = Seq(predicates); 34 | return (...args) => predicateSeq.every(p => p(...args)); 35 | }; 36 | 37 | const everyThen = (func, ...predicates) => (...args) => behavior(Map.of(true, func))(every(...predicates)(...args), ...args); 38 | 39 | const Episode = props => React.createElement( 40 | 'li', 41 | null, 42 | React.createElement( 43 | 'header', 44 | null, 45 | React.createElement( 46 | 'h4', 47 | null, 48 | props.title 49 | ) 50 | ), 51 | React.createElement( 52 | 'section', 53 | null, 54 | React.createElement( 55 | 'p', 56 | null, 57 | React.createElement( 58 | 'strong', 59 | null, 60 | 'Date: ' 61 | ), 62 | props.date 63 | ), 64 | React.createElement( 65 | 'p', 66 | null, 67 | React.createElement( 68 | 'strong', 69 | null, 70 | 'Director: ' 71 | ), 72 | props.director 73 | ), 74 | React.createElement( 75 | 'p', 76 | null, 77 | React.createElement( 78 | 'strong', 79 | null, 80 | 'Rating: ' 81 | ), 82 | props.rating 83 | ) 84 | ) 85 | ); 86 | 87 | class App extends React.Component { 88 | constructor() { 89 | super(); 90 | 91 | this.state = { 92 | episodes, 93 | query: '', 94 | title: true, 95 | date: false, 96 | director: false, 97 | rating: 7 98 | }; 99 | } 100 | 101 | filter(query) { 102 | return every(some(() => query === undefined, () => query === '', every(() => this.state.title, v => v.get('title').includes(query)), every(() => this.state.date, v => v.get('date').includes(query)), every(() => this.state.director, v => v.get('director').includes(query))), v => v.get('rating') >= +this.state.rating); 103 | } 104 | 105 | render() { 106 | return React.createElement( 107 | 'section', 108 | null, 109 | React.createElement( 110 | 'header', 111 | null, 112 | React.createElement( 113 | 'h1', 114 | null, 115 | 'Immutable.js + React Side Effects' 116 | ), 117 | React.createElement('input', { 118 | placeholder: 'filter', 119 | type: 'search', 120 | value: this.state.query, 121 | autoFocus: true, 122 | onInput: e => this.setState({ 123 | query: e.target.value 124 | }) 125 | }) 126 | ), 127 | React.createElement( 128 | 'section', 129 | null, 130 | React.createElement( 131 | 'aside', 132 | null, 133 | React.createElement( 134 | 'section', 135 | null, 136 | React.createElement( 137 | 'header', 138 | null, 139 | React.createElement( 140 | 'h3', 141 | null, 142 | 'Filter Fields' 143 | ) 144 | ), 145 | React.createElement( 146 | 'ul', 147 | null, 148 | React.createElement( 149 | 'li', 150 | null, 151 | React.createElement( 152 | 'label', 153 | null, 154 | 'Title', 155 | React.createElement('input', { 156 | type: 'checkbox', 157 | checked: this.state.title, 158 | onChange: e => this.setState({ 159 | title: e.target.checked 160 | }) 161 | }) 162 | ) 163 | ), 164 | React.createElement( 165 | 'li', 166 | null, 167 | React.createElement( 168 | 'label', 169 | null, 170 | 'Date', 171 | React.createElement('input', { 172 | type: 'checkbox', 173 | checked: this.state.date, 174 | onChange: e => this.setState({ 175 | date: e.target.checked 176 | }) 177 | }) 178 | ) 179 | ), 180 | React.createElement( 181 | 'li', 182 | null, 183 | React.createElement( 184 | 'label', 185 | null, 186 | 'Director', 187 | React.createElement('input', { 188 | type: 'checkbox', 189 | checked: this.state.director, 190 | onChange: e => this.setState({ 191 | director: e.target.checked 192 | }) 193 | }) 194 | ) 195 | ) 196 | ) 197 | ), 198 | React.createElement( 199 | 'section', 200 | null, 201 | React.createElement( 202 | 'header', 203 | null, 204 | React.createElement( 205 | 'h3', 206 | null, 207 | 'Rating' 208 | ) 209 | ), 210 | React.createElement( 211 | 'label', 212 | null, 213 | this.state.rating, 214 | React.createElement('input', { 215 | type: 'range', 216 | min: '1', 217 | max: '10', 218 | value: this.state.rating, 219 | name: 'rating', 220 | onChange: e => this.setState({ 221 | rating: e.target.value 222 | }) 223 | }) 224 | ) 225 | ) 226 | ), 227 | React.createElement( 228 | 'section', 229 | null, 230 | React.createElement( 231 | 'header', 232 | null, 233 | React.createElement( 234 | 'h3', 235 | null, 236 | 'Results' 237 | ) 238 | ), 239 | React.createElement( 240 | 'ul', 241 | { id: 'results' }, 242 | this.state.episodes.filter(this.filter(this.state.query)).toJS().map(v => React.createElement(Episode, _extends({ key: v.title }, v))) 243 | ) 244 | ) 245 | ) 246 | ); 247 | } 248 | } 249 | 250 | /*document.addEventListener('input', everyThen( 251 | e => render(e.target.value), 252 | e => e.target instanceof HTMLInputElement, 253 | e => e.target.type === 'search' 254 | )); 255 | 256 | document.addEventListener('change', everyThen( 257 | () => render(search.value), 258 | e => e.target instanceof HTMLInputElement, 259 | e => [ 260 | 'title', 261 | 'date', 262 | 'director', 263 | 'rating' 264 | ].includes(e.target.name) 265 | )); 266 | 267 | document.addEventListener('change', everyThen( 268 | (e) => { 269 | e.target.parentNode.childNodes[0].nodeValue = e.target.value; 270 | }, 271 | e => e.target instanceof HTMLInputElement, 272 | e => e.target.name === 'rating' 273 | ));*/ 274 | 275 | //render(); 276 | 277 | 278 | const app = ReactDOM.render(React.createElement(App, null), document.getElementById('app')); 279 | -------------------------------------------------------------------------------- /14/immutable-react.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | /* eslint-disable jsx-a11y/label-has-for */ 3 | const { 4 | List, 5 | Map, 6 | Seq 7 | } = Immutable; // eslint-disable-line no-undef 8 | 9 | const React = window.React; 10 | 11 | const episodes = List.of( 12 | Map.of( 13 | 'title', 'The National Anthem', 14 | 'date', 'December 4 2011', 15 | 'director', 'Otto Bathurst', 16 | 'rating', 8.0 17 | ), 18 | Map.of( 19 | 'title', 'Fifteen Million Merits', 20 | 'date', 'December 11 2011', 21 | 'director', 'Euros Lyn', 22 | 'rating', 8.3 23 | ), 24 | Map.of( 25 | 'title', 'The Entire History of You', 26 | 'date', 'December 18 2011', 27 | 'director', 'Brian Welsh', 28 | 'rating', 8.7 29 | ), 30 | Map.of( 31 | 'title', 'Be Right Back', 32 | 'date', 'February 1 2013', 33 | 'director', 'Owen Harris', 34 | 'rating', 8.2 35 | ), 36 | Map.of( 37 | 'title', 'White Bear', 38 | 'date', 'February 18 2013', 39 | 'director', 'Carl Tibbetts', 40 | 'rating', 8.2 41 | ), 42 | Map.of( 43 | 'title', 'The Waldo Moment', 44 | 'date', 'February 25 2013', 45 | 'director', 'Bryn Higgins', 46 | 'rating', 7.0 47 | ), 48 | Map.of( 49 | 'title', 'White Christmas', 50 | 'date', 'December 16 2013', 51 | 'director', 'Carl Tibbetts', 52 | 'rating', 9.1 53 | ), 54 | Map.of( 55 | 'title', 'Nosedive', 56 | 'date', 'October 21 2016', 57 | 'director', 'Joe Wright', 58 | 'rating', 8.3 59 | ), 60 | Map.of( 61 | 'title', 'Playtest', 62 | 'date', 'October 21 2016', 63 | 'director', 'Dan Trachtenberg', 64 | 'rating', 8.2 65 | ), 66 | Map.of( 67 | 'title', 'Shut Up and Dance', 68 | 'date', 'October 21 2016', 69 | 'director', 'James Watkins', 70 | 'rating', 8.5 71 | ), 72 | Map.of( 73 | 'title', 'San Junipero', 74 | 'date', 'October 21 2016', 75 | 'director', 'Owen Harris', 76 | 'rating', 8.8 77 | ), 78 | Map.of( 79 | 'title', 'Men Against Fire', 80 | 'date', 'October 21 2016', 81 | 'director', 'Jakob Verbruggen', 82 | 'rating', 7.9 83 | ), 84 | Map.of( 85 | 'title', 'Hated in the Nation', 86 | 'date', 'October 21 2016', 87 | 'director', 'James Hawes', 88 | 'rating', 8.7 89 | ) 90 | ); 91 | 92 | const some = (...predicates) => { 93 | const predicateSeq = Seq(predicates); 94 | return (...args) => predicateSeq.some(p => p(...args)); 95 | }; 96 | 97 | const every = (...predicates) => { 98 | const predicateSeq = Seq(predicates); 99 | return (...args) => predicateSeq.every(p => p(...args)); 100 | }; 101 | 102 | const Episode = props => ( 103 |
  • 104 |
    105 |

    {props.title}

    106 |
    107 |
    108 |

    Date: {props.date}

    109 |

    Director: {props.director}

    110 |

    Rating: {props.rating}

    111 |
    112 |
  • 113 | ); 114 | 115 | class App extends React.Component { 116 | constructor() { 117 | super(); 118 | 119 | this.state = { 120 | episodes, 121 | query: '', 122 | title: true, 123 | date: false, 124 | director: false, 125 | rating: 7 126 | }; 127 | } 128 | 129 | filter(query) { 130 | return every( 131 | some( 132 | () => query === undefined, 133 | () => query === '', 134 | every( 135 | () => this.state.title, 136 | v => v.get('title').includes(query) 137 | ), 138 | every( 139 | () => this.state.date, 140 | v => v.get('date').includes(query) 141 | ), 142 | every( 143 | () => this.state.director, 144 | v => v.get('director').includes(query) 145 | ) 146 | ), 147 | v => v.get('rating') >= (+this.state.rating) 148 | ); 149 | } 150 | 151 | render() { 152 | return ( 153 |
    154 |
    155 |

    Immutable.js + React Side Effects

    156 | this.setState({ 162 | query: e.target.value 163 | })} 164 | /> 165 |
    166 |
    167 | 230 |
    231 |
    232 |

    Results

    233 |
    234 |
      235 | {this.state.episodes 236 | .filter(this.filter(this.state.query)) 237 | .toJS() 238 | .map(v => ())} 239 |
    240 |
    241 |
    242 |
    243 | ); 244 | } 245 | } 246 | 247 | ReactDOM.render(, document.getElementById('app')); // eslint-disable-line no-undef 248 | -------------------------------------------------------------------------------- /14/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: arial, sans-serif; 3 | margin: 8em; 4 | color: #444; 5 | background: #eee 6 | } 7 | 8 | a:target { 9 | text-decoration: underline; 10 | } 11 | 12 | ul { 13 | list-style-type: none; 14 | padding-left: 0; 15 | } 16 | 17 | ul#results li { 18 | background: #ccced1; 19 | border: 1px solid; 20 | padding: 0.8em; 21 | margin: 1em 0; 22 | } 23 | 24 | ul#results li > section { 25 | display: flex; 26 | justify-content: space-between; 27 | } 28 | 29 | ul#results p { 30 | font-size: 0.9em; 31 | } 32 | -------------------------------------------------------------------------------- /15/01-reading-lines-of-data-into-collections.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const readline = require('readline'); 3 | const { List, Map } = require('immutable'); 4 | 5 | let myMapList = List(); 6 | 7 | const csvInput = readline.createInterface({ 8 | input: fs.createReadStream('./input/01-data.csv') 9 | }); 10 | 11 | csvInput.on('line', (line) => { 12 | myMapList = myMapList.push( 13 | Map.of(...line.split(',')) 14 | ); 15 | }); 16 | 17 | csvInput.on('close', () => { 18 | console.log('myMapList', myMapList.toJS()); 19 | }); 20 | // -> myMapList [ { one: '1', two: '2', three: '3' }, 21 | // -> { four: '4', five: '5', six: '6' }, 22 | // -> { seven: '7', eight: '8', nine: '9' } ] 23 | -------------------------------------------------------------------------------- /15/02-reading-words-concat.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const os = require('os'); 3 | const { List } = require('immutable'); 4 | 5 | let myWordList = List(); 6 | let last = ''; 7 | 8 | console.time('elapsed'); 9 | const wordInput = fs.createReadStream('./input/words'); 10 | 11 | wordInput.on('data', (data) => { 12 | const words = (last + data.toString()).split(os.EOL); 13 | last = words[words.length - 1]; 14 | 15 | myWordList = myWordList.concat(words.slice(0, words.length - 1)); 16 | }); 17 | 18 | wordInput.on('end', () => { 19 | console.log('word count', myWordList.count().toLocaleString()); 20 | console.timeEnd('elapsed'); 21 | // -> word count 235,886 22 | // -> elapsed: 2012.116ms 23 | }); 24 | -------------------------------------------------------------------------------- /15/03-reading-words-push.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const os = require('os'); 3 | const { List } = require('immutable'); 4 | 5 | let myWordList = List(); 6 | let last = ''; 7 | 8 | console.time('elapsed'); 9 | const wordInput = fs.createReadStream('./input/words'); 10 | 11 | wordInput.on('data', (data) => { 12 | const words = `${last}${data.toString()}`.split(os.EOL); 13 | last = words[words.length - 1]; 14 | 15 | for (const word of words) { 16 | myWordList = myWordList.push(word); 17 | } 18 | }); 19 | 20 | wordInput.on('end', () => { 21 | console.log('word count', myWordList.count().toLocaleString()); 22 | console.timeEnd('elapsed'); 23 | // -> word count 235,886 24 | // -> elapsed: 959.299ms 25 | }); 26 | -------------------------------------------------------------------------------- /15/04-reading-words-with-mutations.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const os = require('os'); 3 | const { List } = require('immutable'); 4 | 5 | let myWordList = List(); 6 | let last = ''; 7 | 8 | console.time('elapsed'); 9 | const wordInput = fs.createReadStream('./input/words'); 10 | 11 | wordInput.on('data', (data) => { 12 | const words = `${last}${data.toString()}`.split(os.EOL); 13 | last = words[words.length - 1]; 14 | 15 | myWordList = myWordList.withMutations((list) => { 16 | for (const word of words) { 17 | list.push(word); 18 | } 19 | }); 20 | }); 21 | 22 | wordInput.on('end', () => { 23 | console.log('word count', myWordList.count().toLocaleString()); 24 | console.timeEnd('elapsed'); 25 | // -> word count 235,886 26 | // -> elapsed: 806.099ms 27 | }); 28 | -------------------------------------------------------------------------------- /15/05-writing-collection-data.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const fs = require('fs'); 3 | const { Range } = require('immutable'); 4 | 5 | const myList = Range() 6 | .map(v => `Value${v}`) 7 | .take(20) 8 | .toList(); 9 | const output = fs.createWriteStream( 10 | './output/05-writing-collection-data' 11 | ); 12 | 13 | output.on('close', () => { 14 | console.log('done'); 15 | }); 16 | 17 | myList.forEach((v) => { 18 | output.write(v + os.EOL); 19 | }); 20 | 21 | output.end(); 22 | -------------------------------------------------------------------------------- /15/06-writing-sequence-data.js: -------------------------------------------------------------------------------- 1 | import os from 'os'; 2 | import fs from 'fs'; 3 | import { Range } from 'immutable'; 4 | 5 | const mySeq = Range(1) 6 | .filterNot(v => v % 10) 7 | .take(1000); 8 | 9 | const write = (stream, data) => new Promise((resolve) => { 10 | if (stream.write(data.toString() + os.EOL)) { 11 | resolve(); 12 | } else { 13 | stream.once('drain', () => resolve()); 14 | } 15 | }); 16 | 17 | const output = fs.createWriteStream( 18 | './output/06-writing-sequence-data' 19 | ); 20 | 21 | output.on('close', () => { 22 | console.log('done'); 23 | }); 24 | 25 | mySeq.forEach(async (value) => { 26 | await write(output, value); 27 | }); 28 | 29 | output.end(); 30 | -------------------------------------------------------------------------------- /15/07-chaining-lazy-io-streams.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import os from 'os'; 3 | import { 4 | Writable, 5 | Transform 6 | } from 'stream'; 7 | import { Seq } from 'immutable'; 8 | 9 | const seqTransform = new Transform({ 10 | objectMode: true, 11 | transform(chunk, encoding, callback) { 12 | const last = this.last || ''; 13 | const words = (last + chunk.toString()).split(os.EOL); 14 | 15 | this.last = words[words.length - 1]; 16 | this.push(Seq(words)); 17 | 18 | callback(); 19 | } 20 | }); 21 | 22 | const filterAndMapTransform = new Transform({ 23 | objectMode: true, 24 | transform(seq, encoding, callback) { 25 | seq 26 | .filter(v => v.length >= 15) 27 | .map(v => v.toUpperCase()) 28 | .forEach(v => this.push(v)); 29 | 30 | callback(); 31 | } 32 | }); 33 | 34 | const myWritable = new Writable({ 35 | write(chunk, encoding, callback) { 36 | console.log('writing', chunk.toString()); 37 | callback(); 38 | } 39 | }); 40 | 41 | const wordInput = fs.createReadStream('./input/words'); 42 | 43 | wordInput 44 | .pipe(seqTransform) 45 | .pipe(filterAndMapTransform) 46 | .pipe(myWritable); 47 | -------------------------------------------------------------------------------- /15/input/01-data.csv: -------------------------------------------------------------------------------- 1 | one,1,two,2,three,3 2 | four,4,five,5,six,6 3 | seven,7,eight,8,nine,9 4 | -------------------------------------------------------------------------------- /15/output/05-writing-collection-data: -------------------------------------------------------------------------------- 1 | Value0 2 | Value1 3 | Value2 4 | Value3 5 | Value4 6 | Value5 7 | Value6 8 | Value7 9 | Value8 10 | Value9 11 | Value10 12 | Value11 13 | Value12 14 | Value13 15 | Value14 16 | Value15 17 | Value16 18 | Value17 19 | Value18 20 | Value19 21 | -------------------------------------------------------------------------------- /15/output/06-writing-sequence-data: -------------------------------------------------------------------------------- 1 | 10 2 | 20 3 | 30 4 | 40 5 | 50 6 | 60 7 | 70 8 | 80 9 | 90 10 | 100 11 | 110 12 | 120 13 | 130 14 | 140 15 | 150 16 | 160 17 | 170 18 | 180 19 | 190 20 | 200 21 | 210 22 | 220 23 | 230 24 | 240 25 | 250 26 | 260 27 | 270 28 | 280 29 | 290 30 | 300 31 | 310 32 | 320 33 | 330 34 | 340 35 | 350 36 | 360 37 | 370 38 | 380 39 | 390 40 | 400 41 | 410 42 | 420 43 | 430 44 | 440 45 | 450 46 | 460 47 | 470 48 | 480 49 | 490 50 | 500 51 | 510 52 | 520 53 | 530 54 | 540 55 | 550 56 | 560 57 | 570 58 | 580 59 | 590 60 | 600 61 | 610 62 | 620 63 | 630 64 | 640 65 | 650 66 | 660 67 | 670 68 | 680 69 | 690 70 | 700 71 | 710 72 | 720 73 | 730 74 | 740 75 | 750 76 | 760 77 | 770 78 | 780 79 | 790 80 | 800 81 | 810 82 | 820 83 | 830 84 | 840 85 | 850 86 | 860 87 | 870 88 | 880 89 | 890 90 | 900 91 | 910 92 | 920 93 | 930 94 | 940 95 | 950 96 | 960 97 | 970 98 | 980 99 | 990 100 | 1000 101 | 1010 102 | 1020 103 | 1030 104 | 1040 105 | 1050 106 | 1060 107 | 1070 108 | 1080 109 | 1090 110 | 1100 111 | 1110 112 | 1120 113 | 1130 114 | 1140 115 | 1150 116 | 1160 117 | 1170 118 | 1180 119 | 1190 120 | 1200 121 | 1210 122 | 1220 123 | 1230 124 | 1240 125 | 1250 126 | 1260 127 | 1270 128 | 1280 129 | 1290 130 | 1300 131 | 1310 132 | 1320 133 | 1330 134 | 1340 135 | 1350 136 | 1360 137 | 1370 138 | 1380 139 | 1390 140 | 1400 141 | 1410 142 | 1420 143 | 1430 144 | 1440 145 | 1450 146 | 1460 147 | 1470 148 | 1480 149 | 1490 150 | 1500 151 | 1510 152 | 1520 153 | 1530 154 | 1540 155 | 1550 156 | 1560 157 | 1570 158 | 1580 159 | 1590 160 | 1600 161 | 1610 162 | 1620 163 | 1630 164 | 1640 165 | 1650 166 | 1660 167 | 1670 168 | 1680 169 | 1690 170 | 1700 171 | 1710 172 | 1720 173 | 1730 174 | 1740 175 | 1750 176 | 1760 177 | 1770 178 | 1780 179 | 1790 180 | 1800 181 | 1810 182 | 1820 183 | 1830 184 | 1840 185 | 1850 186 | 1860 187 | 1870 188 | 1880 189 | 1890 190 | 1900 191 | 1910 192 | 1920 193 | 1930 194 | 1940 195 | 1950 196 | 1960 197 | 1970 198 | 1980 199 | 1990 200 | 2000 201 | 2010 202 | 2020 203 | 2030 204 | 2040 205 | 2050 206 | 2060 207 | 2070 208 | 2080 209 | 2090 210 | 2100 211 | 2110 212 | 2120 213 | 2130 214 | 2140 215 | 2150 216 | 2160 217 | 2170 218 | 2180 219 | 2190 220 | 2200 221 | 2210 222 | 2220 223 | 2230 224 | 2240 225 | 2250 226 | 2260 227 | 2270 228 | 2280 229 | 2290 230 | 2300 231 | 2310 232 | 2320 233 | 2330 234 | 2340 235 | 2350 236 | 2360 237 | 2370 238 | 2380 239 | 2390 240 | 2400 241 | 2410 242 | 2420 243 | 2430 244 | 2440 245 | 2450 246 | 2460 247 | 2470 248 | 2480 249 | 2490 250 | 2500 251 | 2510 252 | 2520 253 | 2530 254 | 2540 255 | 2550 256 | 2560 257 | 2570 258 | 2580 259 | 2590 260 | 2600 261 | 2610 262 | 2620 263 | 2630 264 | 2640 265 | 2650 266 | 2660 267 | 2670 268 | 2680 269 | 2690 270 | 2700 271 | 2710 272 | 2720 273 | 2730 274 | 2740 275 | 2750 276 | 2760 277 | 2770 278 | 2780 279 | 2790 280 | 2800 281 | 2810 282 | 2820 283 | 2830 284 | 2840 285 | 2850 286 | 2860 287 | 2870 288 | 2880 289 | 2890 290 | 2900 291 | 2910 292 | 2920 293 | 2930 294 | 2940 295 | 2950 296 | 2960 297 | 2970 298 | 2980 299 | 2990 300 | 3000 301 | 3010 302 | 3020 303 | 3030 304 | 3040 305 | 3050 306 | 3060 307 | 3070 308 | 3080 309 | 3090 310 | 3100 311 | 3110 312 | 3120 313 | 3130 314 | 3140 315 | 3150 316 | 3160 317 | 3170 318 | 3180 319 | 3190 320 | 3200 321 | 3210 322 | 3220 323 | 3230 324 | 3240 325 | 3250 326 | 3260 327 | 3270 328 | 3280 329 | 3290 330 | 3300 331 | 3310 332 | 3320 333 | 3330 334 | 3340 335 | 3350 336 | 3360 337 | 3370 338 | 3380 339 | 3390 340 | 3400 341 | 3410 342 | 3420 343 | 3430 344 | 3440 345 | 3450 346 | 3460 347 | 3470 348 | 3480 349 | 3490 350 | 3500 351 | 3510 352 | 3520 353 | 3530 354 | 3540 355 | 3550 356 | 3560 357 | 3570 358 | 3580 359 | 3590 360 | 3600 361 | 3610 362 | 3620 363 | 3630 364 | 3640 365 | 3650 366 | 3660 367 | 3670 368 | 3680 369 | 3690 370 | 3700 371 | 3710 372 | 3720 373 | 3730 374 | 3740 375 | 3750 376 | 3760 377 | 3770 378 | 3780 379 | 3790 380 | 3800 381 | 3810 382 | 3820 383 | 3830 384 | 3840 385 | 3850 386 | 3860 387 | 3870 388 | 3880 389 | 3890 390 | 3900 391 | 3910 392 | 3920 393 | 3930 394 | 3940 395 | 3950 396 | 3960 397 | 3970 398 | 3980 399 | 3990 400 | 4000 401 | 4010 402 | 4020 403 | 4030 404 | 4040 405 | 4050 406 | 4060 407 | 4070 408 | 4080 409 | 4090 410 | 4100 411 | 4110 412 | 4120 413 | 4130 414 | 4140 415 | 4150 416 | 4160 417 | 4170 418 | 4180 419 | 4190 420 | 4200 421 | 4210 422 | 4220 423 | 4230 424 | 4240 425 | 4250 426 | 4260 427 | 4270 428 | 4280 429 | 4290 430 | 4300 431 | 4310 432 | 4320 433 | 4330 434 | 4340 435 | 4350 436 | 4360 437 | 4370 438 | 4380 439 | 4390 440 | 4400 441 | 4410 442 | 4420 443 | 4430 444 | 4440 445 | 4450 446 | 4460 447 | 4470 448 | 4480 449 | 4490 450 | 4500 451 | 4510 452 | 4520 453 | 4530 454 | 4540 455 | 4550 456 | 4560 457 | 4570 458 | 4580 459 | 4590 460 | 4600 461 | 4610 462 | 4620 463 | 4630 464 | 4640 465 | 4650 466 | 4660 467 | 4670 468 | 4680 469 | 4690 470 | 4700 471 | 4710 472 | 4720 473 | 4730 474 | 4740 475 | 4750 476 | 4760 477 | 4770 478 | 4780 479 | 4790 480 | 4800 481 | 4810 482 | 4820 483 | 4830 484 | 4840 485 | 4850 486 | 4860 487 | 4870 488 | 4880 489 | 4890 490 | 4900 491 | 4910 492 | 4920 493 | 4930 494 | 4940 495 | 4950 496 | 4960 497 | 4970 498 | 4980 499 | 4990 500 | 5000 501 | 5010 502 | 5020 503 | 5030 504 | 5040 505 | 5050 506 | 5060 507 | 5070 508 | 5080 509 | 5090 510 | 5100 511 | 5110 512 | 5120 513 | 5130 514 | 5140 515 | 5150 516 | 5160 517 | 5170 518 | 5180 519 | 5190 520 | 5200 521 | 5210 522 | 5220 523 | 5230 524 | 5240 525 | 5250 526 | 5260 527 | 5270 528 | 5280 529 | 5290 530 | 5300 531 | 5310 532 | 5320 533 | 5330 534 | 5340 535 | 5350 536 | 5360 537 | 5370 538 | 5380 539 | 5390 540 | 5400 541 | 5410 542 | 5420 543 | 5430 544 | 5440 545 | 5450 546 | 5460 547 | 5470 548 | 5480 549 | 5490 550 | 5500 551 | 5510 552 | 5520 553 | 5530 554 | 5540 555 | 5550 556 | 5560 557 | 5570 558 | 5580 559 | 5590 560 | 5600 561 | 5610 562 | 5620 563 | 5630 564 | 5640 565 | 5650 566 | 5660 567 | 5670 568 | 5680 569 | 5690 570 | 5700 571 | 5710 572 | 5720 573 | 5730 574 | 5740 575 | 5750 576 | 5760 577 | 5770 578 | 5780 579 | 5790 580 | 5800 581 | 5810 582 | 5820 583 | 5830 584 | 5840 585 | 5850 586 | 5860 587 | 5870 588 | 5880 589 | 5890 590 | 5900 591 | 5910 592 | 5920 593 | 5930 594 | 5940 595 | 5950 596 | 5960 597 | 5970 598 | 5980 599 | 5990 600 | 6000 601 | 6010 602 | 6020 603 | 6030 604 | 6040 605 | 6050 606 | 6060 607 | 6070 608 | 6080 609 | 6090 610 | 6100 611 | 6110 612 | 6120 613 | 6130 614 | 6140 615 | 6150 616 | 6160 617 | 6170 618 | 6180 619 | 6190 620 | 6200 621 | 6210 622 | 6220 623 | 6230 624 | 6240 625 | 6250 626 | 6260 627 | 6270 628 | 6280 629 | 6290 630 | 6300 631 | 6310 632 | 6320 633 | 6330 634 | 6340 635 | 6350 636 | 6360 637 | 6370 638 | 6380 639 | 6390 640 | 6400 641 | 6410 642 | 6420 643 | 6430 644 | 6440 645 | 6450 646 | 6460 647 | 6470 648 | 6480 649 | 6490 650 | 6500 651 | 6510 652 | 6520 653 | 6530 654 | 6540 655 | 6550 656 | 6560 657 | 6570 658 | 6580 659 | 6590 660 | 6600 661 | 6610 662 | 6620 663 | 6630 664 | 6640 665 | 6650 666 | 6660 667 | 6670 668 | 6680 669 | 6690 670 | 6700 671 | 6710 672 | 6720 673 | 6730 674 | 6740 675 | 6750 676 | 6760 677 | 6770 678 | 6780 679 | 6790 680 | 6800 681 | 6810 682 | 6820 683 | 6830 684 | 6840 685 | 6850 686 | 6860 687 | 6870 688 | 6880 689 | 6890 690 | 6900 691 | 6910 692 | 6920 693 | 6930 694 | 6940 695 | 6950 696 | 6960 697 | 6970 698 | 6980 699 | 6990 700 | 7000 701 | 7010 702 | 7020 703 | 7030 704 | 7040 705 | 7050 706 | 7060 707 | 7070 708 | 7080 709 | 7090 710 | 7100 711 | 7110 712 | 7120 713 | 7130 714 | 7140 715 | 7150 716 | 7160 717 | 7170 718 | 7180 719 | 7190 720 | 7200 721 | 7210 722 | 7220 723 | 7230 724 | 7240 725 | 7250 726 | 7260 727 | 7270 728 | 7280 729 | 7290 730 | 7300 731 | 7310 732 | 7320 733 | 7330 734 | 7340 735 | 7350 736 | 7360 737 | 7370 738 | 7380 739 | 7390 740 | 7400 741 | 7410 742 | 7420 743 | 7430 744 | 7440 745 | 7450 746 | 7460 747 | 7470 748 | 7480 749 | 7490 750 | 7500 751 | 7510 752 | 7520 753 | 7530 754 | 7540 755 | 7550 756 | 7560 757 | 7570 758 | 7580 759 | 7590 760 | 7600 761 | 7610 762 | 7620 763 | 7630 764 | 7640 765 | 7650 766 | 7660 767 | 7670 768 | 7680 769 | 7690 770 | 7700 771 | 7710 772 | 7720 773 | 7730 774 | 7740 775 | 7750 776 | 7760 777 | 7770 778 | 7780 779 | 7790 780 | 7800 781 | 7810 782 | 7820 783 | 7830 784 | 7840 785 | 7850 786 | 7860 787 | 7870 788 | 7880 789 | 7890 790 | 7900 791 | 7910 792 | 7920 793 | 7930 794 | 7940 795 | 7950 796 | 7960 797 | 7970 798 | 7980 799 | 7990 800 | 8000 801 | 8010 802 | 8020 803 | 8030 804 | 8040 805 | 8050 806 | 8060 807 | 8070 808 | 8080 809 | 8090 810 | 8100 811 | 8110 812 | 8120 813 | 8130 814 | 8140 815 | 8150 816 | 8160 817 | 8170 818 | 8180 819 | 8190 820 | 8200 821 | 8210 822 | 8220 823 | 8230 824 | 8240 825 | 8250 826 | 8260 827 | 8270 828 | 8280 829 | 8290 830 | 8300 831 | 8310 832 | 8320 833 | 8330 834 | 8340 835 | 8350 836 | 8360 837 | 8370 838 | 8380 839 | 8390 840 | 8400 841 | 8410 842 | 8420 843 | 8430 844 | 8440 845 | 8450 846 | 8460 847 | 8470 848 | 8480 849 | 8490 850 | 8500 851 | 8510 852 | 8520 853 | 8530 854 | 8540 855 | 8550 856 | 8560 857 | 8570 858 | 8580 859 | 8590 860 | 8600 861 | 8610 862 | 8620 863 | 8630 864 | 8640 865 | 8650 866 | 8660 867 | 8670 868 | 8680 869 | 8690 870 | 8700 871 | 8710 872 | 8720 873 | 8730 874 | 8740 875 | 8750 876 | 8760 877 | 8770 878 | 8780 879 | 8790 880 | 8800 881 | 8810 882 | 8820 883 | 8830 884 | 8840 885 | 8850 886 | 8860 887 | 8870 888 | 8880 889 | 8890 890 | 8900 891 | 8910 892 | 8920 893 | 8930 894 | 8940 895 | 8950 896 | 8960 897 | 8970 898 | 8980 899 | 8990 900 | 9000 901 | 9010 902 | 9020 903 | 9030 904 | 9040 905 | 9050 906 | 9060 907 | 9070 908 | 9080 909 | 9090 910 | 9100 911 | 9110 912 | 9120 913 | 9130 914 | 9140 915 | 9150 916 | 9160 917 | 9170 918 | 9180 919 | 9190 920 | 9200 921 | 9210 922 | 9220 923 | 9230 924 | 9240 925 | 9250 926 | 9260 927 | 9270 928 | 9280 929 | 9290 930 | 9300 931 | 9310 932 | 9320 933 | 9330 934 | 9340 935 | 9350 936 | 9360 937 | 9370 938 | 9380 939 | 9390 940 | 9400 941 | 9410 942 | 9420 943 | 9430 944 | 9440 945 | 9450 946 | 9460 947 | 9470 948 | 9480 949 | 9490 950 | 9500 951 | 9510 952 | 9520 953 | 9530 954 | 9540 955 | 9550 956 | 9560 957 | 9570 958 | 9580 959 | 9590 960 | 9600 961 | 9610 962 | 9620 963 | 9630 964 | 9640 965 | 9650 966 | 9660 967 | 9670 968 | 9680 969 | 9690 970 | 9700 971 | 9710 972 | 9720 973 | 9730 974 | 9740 975 | 9750 976 | 9760 977 | 9770 978 | 9780 979 | 9790 980 | 9800 981 | 9810 982 | 9820 983 | 9830 984 | 9840 985 | 9850 986 | 9860 987 | 9870 988 | 9880 989 | 9890 990 | 9900 991 | 9910 992 | 9920 993 | 9930 994 | 9940 995 | 9950 996 | 9960 997 | 9970 998 | 9980 999 | 9990 1000 | 10000 1001 | -------------------------------------------------------------------------------- /16/immutable-app.js: -------------------------------------------------------------------------------- 1 | const { List } = Immutable; // eslint-disable-line no-undef 2 | 3 | const App = (initialState, sideEffects) => { // eslint-disable-line no-unused-vars 4 | let state = initialState; 5 | 6 | state.forEach((v, k) => { 7 | List() 8 | .concat(sideEffects.get(k)) 9 | .forEach(sideEffect => sideEffect(v)); 10 | }); 11 | 12 | return (updater) => { 13 | const newState = updater(state); 14 | 15 | newState.forEach((v, k) => { 16 | if (v !== state.get(k)) { 17 | List() 18 | .concat(sideEffects.get(k)) 19 | .forEach(sideEffect => sideEffect(v)); 20 | } 21 | }); 22 | 23 | state = newState; 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /16/immutable-dom.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Immutable.js + DOM Side Effects 6 | 7 | 8 | 9 | 10 | 11 | 12 | 24 |
    25 |

    Immutable.js + DOM Side Effects

    26 | 27 |
    28 |
    29 | 65 |
    66 |
    67 |

    0 Results

    68 |
    69 |
      70 |
      71 |
      72 |
      73 |

      Create Episode

      74 |
      75 |
      76 |
      77 | 81 |
      82 |
      83 | 87 |
      88 |
      89 | 93 |
      94 |
      95 | 100 |
      101 |
      102 | 103 |
      104 |
      105 |
      106 |
      107 | 108 | 109 | -------------------------------------------------------------------------------- /16/immutable-dom.js: -------------------------------------------------------------------------------- 1 | const { 2 | List, 3 | Map, 4 | Seq 5 | } = Immutable; // eslint-disable-line no-undef 6 | 7 | const App = (initialState, sideEffects) => { 8 | let state = initialState; 9 | 10 | state.forEach((v, k) => { 11 | List() 12 | .concat(sideEffects.get(k)) 13 | .forEach(sideEffect => sideEffect(state.get(k))); 14 | }); 15 | 16 | return (updater) => { 17 | const newState = updater(state); 18 | 19 | newState.forEach((v, k) => { 20 | if (v !== state.get(k)) { 21 | List() 22 | .concat(sideEffects.get(k)) 23 | .forEach(sideEffect => sideEffect(newState.get(k))); 24 | } 25 | }); 26 | 27 | state = newState; 28 | }; 29 | }; 30 | 31 | const removeChildren = (element) => { 32 | while (element.firstChild) { 33 | element.removeChild(element.firstChild); 34 | } 35 | }; 36 | 37 | const some = (...predicates) => { 38 | const predicateSeq = Seq(predicates); 39 | return (...args) => predicateSeq.some(p => p(...args)); 40 | }; 41 | 42 | const every = (...predicates) => { 43 | const predicateSeq = Seq(predicates); 44 | return (...args) => predicateSeq.every(p => p(...args)); 45 | }; 46 | 47 | const filter = ({ 48 | query, 49 | title, 50 | date, 51 | director, 52 | rating 53 | }) => every( 54 | some( 55 | () => query === undefined, 56 | () => query === '', 57 | every( 58 | () => title, 59 | v => v.get('title').includes(query) 60 | ), 61 | every( 62 | () => date, 63 | v => v.get('date').includes(query) 64 | ), 65 | every( 66 | () => director, 67 | v => v.get('director').includes(query) 68 | ) 69 | ), 70 | v => v.get('rating') >= (+rating) 71 | ); 72 | 73 | const app = App( 74 | Map.of( 75 | 'results', Map.of( 76 | 'episodes', List.of( 77 | Map.of( 78 | 'title', 'The National Anthem', 79 | 'date', 'December 4 2011', 80 | 'director', 'Otto Bathurst', 81 | 'rating', 8.0 82 | ), 83 | Map.of( 84 | 'title', 'Fifteen Million Merits', 85 | 'date', 'December 11 2011', 86 | 'director', 'Euros Lyn', 87 | 'rating', 8.3 88 | ), 89 | Map.of( 90 | 'title', 'The Entire History of You', 91 | 'date', 'December 18 2011', 92 | 'director', 'Brian Welsh', 93 | 'rating', 8.7 94 | ), 95 | Map.of( 96 | 'title', 'Be Right Back', 97 | 'date', 'February 1 2013', 98 | 'director', 'Owen Harris', 99 | 'rating', 8.2 100 | ), 101 | Map.of( 102 | 'title', 'White Bear', 103 | 'date', 'February 18 2013', 104 | 'director', 'Carl Tibbetts', 105 | 'rating', 8.2 106 | ), 107 | Map.of( 108 | 'title', 'The Waldo Moment', 109 | 'date', 'February 25 2013', 110 | 'director', 'Bryn Higgins', 111 | 'rating', 7.0 112 | ), 113 | Map.of( 114 | 'title', 'White Christmas', 115 | 'date', 'December 16 2013', 116 | 'director', 'Carl Tibbetts', 117 | 'rating', 9.1 118 | ), 119 | Map.of( 120 | 'title', 'Nosedive', 121 | 'date', 'October 21 2016', 122 | 'director', 'Joe Wright', 123 | 'rating', 8.3 124 | ), 125 | Map.of( 126 | 'title', 'Playtest', 127 | 'date', 'October 21 2016', 128 | 'director', 'Dan Trachtenberg', 129 | 'rating', 8.2 130 | ), 131 | Map.of( 132 | 'title', 'Shut Up and Dance', 133 | 'date', 'October 21 2016', 134 | 'director', 'James Watkins', 135 | 'rating', 8.5 136 | ), 137 | Map.of( 138 | 'title', 'San Junipero', 139 | 'date', 'October 21 2016', 140 | 'director', 'Owen Harris', 141 | 'rating', 8.8 142 | ), 143 | Map.of( 144 | 'title', 'Men Against Fire', 145 | 'date', 'October 21 2016', 146 | 'director', 'Jakob Verbruggen', 147 | 'rating', 7.9 148 | ), 149 | Map.of( 150 | 'title', 'Hated in the Nation', 151 | 'date', 'October 21 2016', 152 | 'director', 'James Hawes', 153 | 'rating', 8.7 154 | ) 155 | ), 156 | 'query', '', 157 | 'title', true, 158 | 'director', false, 159 | 'date', false, 160 | 'rating', 7 161 | ), 162 | 'create', Map.of( 163 | 'title', '', 164 | 'director', '', 165 | 'date', '', 166 | 'rating', 0 167 | ) 168 | ), 169 | Map.of( 170 | 'results', Seq.of( 171 | (results) => { 172 | removeChildren( 173 | document 174 | .getElementById('results') 175 | .querySelector('ul') 176 | ); 177 | 178 | results 179 | .get('episodes') 180 | .toSeq() 181 | .filter(filter(results.toJS())) 182 | .forEach((v) => { 183 | const { content } = document 184 | .getElementById('episode-template'); 185 | 186 | content 187 | .querySelector('h4') 188 | .textContent = v.get('title'); 189 | content 190 | .querySelector('p:nth-of-type(1)') 191 | .childNodes[1] 192 | .nodeValue = v.get('date'); 193 | content 194 | .querySelector('p:nth-of-type(2)') 195 | .childNodes[1] 196 | .nodeValue = v.get('director'); 197 | content 198 | .querySelector('p:nth-of-type(3)') 199 | .childNodes[1] 200 | .nodeValue = v.get('rating'); 201 | 202 | document 203 | .getElementById('results') 204 | .querySelector('ul') 205 | .appendChild(document.importNode(content, true)); 206 | }); 207 | }, 208 | (results) => { 209 | const count = results 210 | .get('episodes') 211 | .toSeq() 212 | .filter(filter(results.toJS())) 213 | .count(); 214 | 215 | document 216 | .getElementById('results') 217 | .querySelector('h3') 218 | .textContent = document 219 | .getElementById('results') 220 | .querySelector('h3') 221 | .textContent 222 | .replace(/\d*/, count); 223 | }, 224 | (results) => { 225 | document 226 | .querySelector('input[name="rating"]') 227 | .parentNode 228 | .childNodes[0] 229 | .nodeValue = results.get('rating'); 230 | } 231 | ), 232 | 'create', Seq.of( 233 | (create) => { 234 | document 235 | .getElementById('new-episode-rating') 236 | .value = create.get('rating'); 237 | document 238 | .querySelector('form[name="create-episode"] input[name="title"]') 239 | .value = create.get('title'); 240 | document 241 | .querySelector('form[name="create-episode"] input[name="director"]') 242 | .value = create.get('director'); 243 | document 244 | .querySelector('form[name="create-episode"] input[name="date"]') 245 | .value = create.get('date'); 246 | document 247 | .querySelector('output[for="new-episode-rating"]') 248 | .value = create.get('rating'); 249 | } 250 | ) 251 | ) 252 | ); 253 | 254 | document 255 | .querySelector('header input[type="search"]') 256 | .addEventListener('input', e => app(state => state.setIn( 257 | ['results', 'query'], 258 | e.target.value 259 | ))); 260 | 261 | document 262 | .querySelector('li input[name="title"]') 263 | .addEventListener('change', e => app(state => state.setIn( 264 | ['results', 'title'], 265 | e.target.checked 266 | ))); 267 | 268 | document 269 | .querySelector('li input[name="director"]') 270 | .addEventListener('change', e => app(state => state.setIn( 271 | ['results', 'director'], 272 | e.target.checked 273 | ))); 274 | 275 | document 276 | .querySelector('li input[name="date"]') 277 | .addEventListener('change', e => app(state => state.setIn( 278 | ['results', 'date'], 279 | e.target.checked 280 | ))); 281 | 282 | document 283 | .getElementById('filter-rating') 284 | .addEventListener('change', e => app(state => state.setIn( 285 | ['results', 'rating'], 286 | e.target.value 287 | ))); 288 | 289 | document 290 | .querySelector('form[name="create-episode"] input[name="title"]') 291 | .addEventListener('input', e => app(state => state.setIn( 292 | ['create', 'title'], 293 | e.target.value 294 | ))); 295 | 296 | document 297 | .querySelector('form[name="create-episode"] input[name="director"]') 298 | .addEventListener('input', e => app(state => state.setIn( 299 | ['create', 'director'], 300 | e.target.value 301 | ))); 302 | 303 | document 304 | .querySelector('form[name="create-episode"] input[name="date"]') 305 | .addEventListener('input', e => app(state => state.setIn( 306 | ['create', 'date'], 307 | e.target.value 308 | ))); 309 | 310 | document 311 | .getElementById('new-episode-rating') 312 | .addEventListener('change', e => app(state => state.setIn( 313 | ['create', 'rating'], 314 | e.target.value 315 | ))); 316 | 317 | document 318 | .querySelector('form[name="create-episode"]') 319 | .addEventListener('submit', (e) => { 320 | e.preventDefault(); 321 | app(state => state 322 | .updateIn( 323 | ['results', 'episodes'], 324 | episodes => episodes.push( 325 | Map(state.get('create').toJS()) 326 | ) 327 | ) 328 | .setIn(['create', 'title'], '') 329 | .setIn(['create', 'director'], '') 330 | .setIn(['create', 'date'], '') 331 | ); 332 | }); 333 | -------------------------------------------------------------------------------- /16/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: arial, sans-serif; 3 | margin: 8em; 4 | color: #444; 5 | background: #eee 6 | } 7 | 8 | a:target { 9 | text-decoration: underline; 10 | } 11 | 12 | ul { 13 | list-style-type: none; 14 | padding-left: 0; 15 | } 16 | 17 | section#results ul li { 18 | background: #ccced1; 19 | border: 1px solid; 20 | padding: 0.8em; 21 | margin: 1em 0; 22 | } 23 | 24 | section#results ul li > section { 25 | display: flex; 26 | justify-content: space-between; 27 | } 28 | 29 | section#results p { 30 | font-size: 0.9em; 31 | } 32 | 33 | form { 34 | display: flex; 35 | justify-content: space-between; 36 | } 37 | 38 | form section { 39 | 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Packt 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 | 2 | 3 | 4 | ## $5 Tech Unlocked 2021! 5 | [Buy and download this Book for only $5 on PacktPub.com](https://www.packtpub.com/product/mastering-immutable-js/9781788395113) 6 | ----- 7 | *If you have read this book, please leave a review on [Amazon.com](https://www.amazon.com/gp/product/1788395115). Potential readers can then use your unbiased opinion to help them make purchase decisions. Thank you. The $5 campaign runs from __December 15th 2020__ to __January 13th 2021.__* 8 | 9 | # Mastering-Immutablejs 10 | Mastering Immutable.js by Packt 11 | 12 | ## Installation 13 | Make sure [Node.js](https://nodejs.org/) is installed. Then, clone this 14 | repository: 15 | ```bash 16 | git clone https://github.com/PacktPublishing/Mastering-Immutablejs.git 17 | ``` 18 | 19 | Change directories and install: 20 | ```bash 21 | cd Mastering-Immutablejs 22 | npm install 23 | ``` 24 | 25 | ## Running examples 26 | Since the code examples use `import`, you need to compile the source before 27 | running it. The easiest way to do this is compiling it on-the-fly using 28 | `babel-node`: 29 | ```bash 30 | npm run babel-node 02/01-available-types.js 31 | ``` 32 | 33 | Chapters 14 and 16 have examples that only work in the browser. For these, 34 | you can just open up the HTML files in these directories right in the browser. 35 | ### Download a free PDF 36 | 37 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
      Simply click on the link to claim your free PDF.
      38 |

      https://packt.link/free-ebook/9781788395113

      -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mastering-immutable", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "babel-node": "babel-node", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "Adam Boduch", 11 | "license": "ISC", 12 | "dependencies": { 13 | "babel-cli": "^6.26.0", 14 | "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", 15 | "babel-plugin-transform-react-jsx": "^6.24.1", 16 | "babel-runtime": "^6.22.0", 17 | "eslint": "^3.19.0", 18 | "eslint-config-airbnb": "^14.1.0", 19 | "eslint-plugin-import": "^2.2.0", 20 | "eslint-plugin-jsx-a11y": "^4.0.0", 21 | "eslint-plugin-react": "^6.10.3", 22 | "immutable": "^4.0.0-rc.2", 23 | "install": "^0.9.6", 24 | "npm": "^4.5.0", 25 | "react": "^15.6.1", 26 | "react-dom": "^15.6.1" 27 | } 28 | } 29 | --------------------------------------------------------------------------------