Иммутабельность

Лекция 27

Immutability - Pros

  • код предсказуемей
  • быстрое сравнение
  • безопасность многопоточности
  • облегчает работу GC засчёт отмены defensive copy
  • путешествия во времени

Immutability - Cons

  • может упасть производительность
  • может вырасти потребление памяти
  • написание требует больше времени

Immutable.js


// https://facebook.github.io/immutable-js
npm install immutable
      

Immutable.js - Example


import { Map } from 'immutable';

const a = Map({
  name: 'Carl Grimes',
  status: 'alive'
});

const b = a.set('status', 'dead');

console.log(a.get('name'), a.get('status'));
console.log(b.get('name'), b.get('status'));
console.log(a !== b);
      

Immutable.js - List


const emptyList = List()

const listFromPlainArray = List([ 1, 2, 3, 4 ])

const plainSet = Set([ 1, 2, 3, 4 ])
const listFromPlainSet = List(plainSet)

const arrayIterator = plainArray[Symbol.iterator]()
const listFromCollectionArray = List(arrayIterator)
      

Immutable.js - List


List.isList([]); // false
List.isList(List()); // true
      

Immutable.js - List


List.of(1, 2, 3, 4)
List.of({x:1}, 2, [3], 4)
      

Immutable.js - List


const originalList = List([ 0 ]);
// List [ 0 ]

originalList.set(1, 1);
// List [ 0, 1 ]

originalList.set(0, 'overwritten');
// List [ "overwritten" ]

originalList.set(2, 2);
// List [ 0, undefined, 2 ]
      

Immutable.js - List


List([ 0, 1, 2, 3, 4 ]).delete(0);
// List [ 1, 2, 3, 4 ]

List([ 0, 1, 2, 3, 4 ]).delete(-1);
// List [ 0, 1, 2, 3 ]
      

Immutable.js - List


List([ 1, 2, 3, 4 ]).clear()
// List []
      

Immutable.js - List


List([ 1, 2, 3, 4 ]).push(5)
// List [ 1, 2, 3, 4, 5 ]
      

Immutable.js - List


List([ 1, 2, 3, 4 ]).pop()
// List[ 1, 2, 3 ]
      

Immutable.js - List


List([ 2, 3, 4]).unshift(1);
// List [ 1, 2, 3, 4 ]
      

Immutable.js - List


const list = List([ 'a', 'b', 'c' ])
const result = list.update(2, val => val + val)
// List [ "a", "b", "cc" ]
      

Immutable.js - List


const list = List([ 0, 1, 2, List([ 3, 4 ])])
list.setIn([3, 0], 999);
// List [ 0, 1, 2, List [ 999, 4 ] ]
      

Immutable.js - List


const list = List([ 0, 1, 2, { plain: 'object' }])
list.setIn([3, 'plain'], 'value');
// List([ 0, 1, 2, { plain: 'value' }])
      

Immutable.js - List


const list = List([ 0, 1, 2, List([ 3, 4 ])])
list.deleteIn([3, 0]);
// List [ 0, 1, 2, List [ 4 ] ]
      

Immutable.js - List


const list = List([ 0, 1, 2, { plain: 'object' }])
list.removeIn([3, 'plain']);
// List([ 0, 1, 2, {}])
      

Immutable.js - List


const list = List(['a', 'b', 'c'])
list.concat(['d', 'e'])
// ["a", "b", "c", "d", "e"]
      

Immutable.js - List


List([ 1, 2 ]).map(x => 10 * x)
// List [ 10, 20 ]
      

Immutable.js - List


List(['a', List(['b', 'c'])]).flatten(true)
// ['a', 'b', 'c']
      

Immutable.js - List


const list = List([1, 2, 3])

list.flatMap(x => List([x, x]))
list.map(x => List([x, x])).flatten(true)
// [1, 1, 2, 2, 3, 3]
      

Immutable.js - List


List([1, 2, 3, 4]).filter(x => x % 2 === 0)
// [2, 4]
      

Immutable.js - List


List([1, 2, 3, 4]).filterNot(x => x % 2 === 0)
// [1, 3]
      

Immutable.js - List


const a = List([ 1, 2, 3 ]);
const b = List([ 4, 5, 6 ]);
const c = a.zip(b);
// List [ [ 1, 4 ], [ 2, 5 ], [ 3, 6 ] ]
      

Immutable.js - List


const a = List([ 1, 2 ]);
const b = List([ 3, 4, 5 ]);
const c = a.zipAll(b);
// List [ [ 1, 3 ], [ 2, 4 ], [ undefined, 5 ] ]
      

Immutable.js - List


const a = List([ 1, 2, 3 ]);
const b = List([ 4, 5, 6 ]);
const c = a.zipWith((a, b) => a + b, b);
// List [ 5, 7, 9 ]
      

Immutable.js - List


reverse()
sort(comparator?)
sortBy(comparatorValueMapper, comparator?)
groupBy()
      

Immutable.js - List


toJS() // deep
toArray() // shallow
toJSON()
toObject() // indexes = keys
      

Immutable.js - List


get(index)
has(index)
getIn(path)
hasIn(path)
includes(value)
first()
last()
min()
max()
      

Immutable.js - List


indexOf()
lastIndexOf()
findIndex()
findLastIndex()
find()
findLast()
every()
some()
      

Immutable.js - List


List([1, 2, 3]).interpose('x')
// [1, "x", 2, "x", 3]
      

Immutable.js - List


List([ 1, 2, 3 ]).interleave(
  List([ 'A', 'B', 'C' ])
)
// List [ 1, "A", 2, "B", 3, "C"" ]

List([ 1, 2, 3 ]).interleave(
  List([ 'A', 'B' ]),
  List([ 'X', 'Y', 'Z' ])
)
// List [ 1, "A", "X", 2, "B", "Y"" ]
      

Immutable.js - List


List([1, 2, 3]).rest()
// [2, 3]

List([1, 2, 3]).butLast()
// [1, 2]
      

Immutable.js - List


List([1, 2, 3]).skip(2)
// [3]

List([1, 2, 3]).skipLast(2)
// [1]
      

Immutable.js - List


List([ 'dog', 'frog', 'cat', 'hat', 'god' ])
  .skipWhile(x => x.match(/g/))
// List [ "cat", "hat", "god"" ]

List([ 'dog', 'frog', 'cat', 'hat', 'god' ])
  .skipUntil(x => x.match(/hat/))
// List [ "hat", "god"" ]
      

Immutable.js - List


List([1, 2, 3]).take(2)
// [1, 2]

List([1, 2, 3]).takeLast(2)
// [2, 3]
      

Immutable.js - List


List([ 'dog', 'frog', 'cat', 'hat', 'god' ])
  .takeWhile(x => x.match(/o/))
// List [ "dog", "frog" ]

List([ 'dog', 'frog', 'cat', 'hat', 'god' ])
  .takeUntil(x => x.match(/at/))
// List [ "dog", "frog" ]
      

Immutable.js - Map


Map({ key: "value" })
Map([ [ "key", "value" ] ])
      

Immutable.js - Map


get(key)
getIn(path)
set(key, value)
setIn(path, value)
delete(key)
deleteIn(path, key)
deleteAll([ keys ])
clear()
      

Immutable.js - Map


const aMap = Map({ key: 'value' })

aMap.update('key', value => value + value)
// { "key": "valuevalue" }
      

Immutable.js - Map


const one = Map({ a: 10, b: 20, c: 30 })
const two = Map({ b: 40, a: 50, d: 60 })

one.merge(two)
// Map { "a": 50, "b": 40, "c": 30, "d": 60 }

two.merge(one)
// Map { "b": 20, "a": 10, "d": 60, "c": 30 }
      

Immutable.js - Map


const one = Map({ a: 10, b: 20, c: 30 })
const two = Map({ b: 40, a: 50, d: 60 })

one.mergeWith((x, y) => x / y, two)
// { "a": 0.2, "b": 0.5, "c": 30, "d": 60 }

two.mergeWith((x, y) => x / y, one)
// { "b": 2, "a": 5, "d": 60, "c": 30 }
      

Immutable.js - Map


const one = Map({
  a: Map({ x: 10, y: 10 }),
  b: Map({ x: 20, y: 50 })
})

const two = Map({
  a: Map({ x: 2 }),
  b: Map({ y: 5 }),
  c: Map({ z: 3 })
})

one.mergeDeep(two)
/  {
//   "a": { "x": 2, "y": 10 },
//   "b": { "x": 20, "y": 5 },
//   "c": { "z": 3 }
// }
      

Immutable.js - Map


const map1 = Map()

const map2 = map1.withMutations(map => {
  map.set('a', 1).set('b', 2).set('c', 3)
})

assert.equal(map1.size, 0)
assert.equal(map2.size, 3)
      

Immutable.js - Map


const map = Map();

const map1 = map.asMutable();
const map2 = map1.set('a', 1);
const map3 = map1.set('b', 2);
const map4 = map1.asImmutable();
const map5 = map4.set('c', 3);

console.log(map1 === map2);
console.log(map1 === map3);
console.log(map1 === map4);
console.log(map5 !== map4);
      

Immutable.js - Map


Map({ a: 1, b: 2 }).map(x => 10 * x)
// Map { a: 10, b: 20 }
      

Immutable.js - Map


Map({ a: 1, b: 2 }).mapKeys(x => x.toUpperCase())
// Map { "A": 1, "B": 2 }
      

Immutable.js - Map


Map({ a: 1, b: 2 })
  .mapEntries(([ k, v ]) =>
      [ k.toUpperCase(), v * 2 ])
// Map { "A": 2, "B": 4 }
      

Immutable.js - Map


const map = Map({ a: 1, b: 2, c: 3 });

map.filter(v => v !== 1)
map.filter((v, k) => k !== 'a')
// { b: 2, c: 3 }

map.filterNot(v => v !== 1)
map.filterNot((v, k) => k !== 'a')
// { a: 1 }
      

Immutable.js - Map


Map({ a: 1, b: 2, c: 3 }).flip()
// { 1: 'a', 2: 'b', 3: 'c' }
      

Immutable.js - Map


reverse()
sort()
sortBy()
groupBy()
toJS()
toJSON()
toArray()
toObject()
equals()
      

Immutable.js - Map


includes()
first()
last()
      

Immutable.js - Set


let s = Set();

s = s.add('a');
s = s.add('a');
// ['a']
      

Immutable.js - Stack


shift() / pop() // O(1)
unshift() / push() // O(1)
      

Immutable.js - Range


Range() // [ 0, 1, 2, 3, ... ]
Range(10) // [ 10, 11, 12, 13, ... ]
Range(10, 15) // [ 10, 11, 12, 13, 14 ]
Range(10, 30, 5) // [ 10, 15, 20, 25 ]
Range(30, 10, 5) // [ 30, 25, 20, 15 ]
Range(30, 30, 5) // []
      

Immutable.js - Record


const ABRecordFactory = Record({ a: 1, b: 2 })
const myRecord = new ABRecordFactory({ b: 3, c: 4 })
// { a: 1, b: 3 }
      

Immutable.js - Sequence


const oddSquares = Seq([1, 2, 3, 4, 5, 6, 7, 8])
  .filter(x => {
    console.log('Lazy');
    return x % 2 !== 0
  })
      

Immutable.js - Sequence


const oddSquares = Seq([1, 2, 3, 4, 5, 6, 7, 8])
  .filter(x => {
    console.log('filter', x);
    return x % 2 !== 0
  })


oddSquares.get(1);
// filter 1
// filter 2
// filter 3
      

Immutable.js - Sequence


Range(1, Infinity)
  .skip(1000)
  .map(n => -n)
  .filter(n => n % 2 === 0)
  .take(2)
  // lazy / after iterate
  // [-1002, -1004]