Slide 1

Slide 1 text

Immutable Data Structures Breno Ferreira @breno_ferreira

Slide 2

Slide 2 text

Functional Programming Mais que funções, é sobre controlar side-effects Usar Map/Filter/Reduce é legal. É legal usar underscore/lodash/etc.. e usar uma variedade de funções auxiliares. Mas FP é bem mais que isso. FP é uma mudança de mindset Em praticamente todas as linguagens funcionais, o mindset do desenvolvedor é orientado por funções puras (sem side-effects), ou seja: dada uma entrada, ela irá, via de regra, sempre retornar o mesmo valor. E isso leva ao uso de estruturas de dados imutáveis.

Slide 3

Slide 3 text

Dados mutáveis são side-effects Não desejados na maioria das vezes function soma(a, b) { lancaMissilNuclear(); return a + b; } Dados mutáveis em muitos casos dificultam a compreensão do código, principalmente quando não são explícitos para o caller da função. Exemplo de side-effects nao explicito function soma(a, b) { lancaMissilNuclear(); return a + b; } Em tempo de execução, uma série de mutações no estado de um objeto também dificulta a compreensão do data-flow da aplicação.

Slide 4

Slide 4 text

Mudanças de estado são naturais Poucas aplicações são read-only, não é mesmo? Se fosse tudo conteúdo estático, nao precisaria de JS. HTML/CSS, dá pro designer fazer e tá tudo ok.

Slide 5

Slide 5 text

> let obj = {a: 1, b: 2}; { a: 1, b: 2 } > const immutableObj = Object.freeze(obj); { a: 1, b: 2 } > immutableObj.a = 3 { a: 1, b: 2 } Object.freeze retorna um objetivo imutável. Mas será que ele é útil? Não é explícita a diferença entre um objeto Frozen e Não-Frozen.

Slide 6

Slide 6 text

> let obj = {a: 1, b: 2}; { a: 1, b: 2 } > const immutableObj = Object.freeze(obj); { a: 1, b: 2 } > changed = Object.assign({}, immutableObj, {a:3}) { a: 3, b: 2 } Usando Object.assign, estamos criando cópias de toda a estrutura do objeto. Usado com muita frequencia, iremos causar uso alto de memória, e uma pressão alta no Garbage Collector. Fora que essa API é horrível

Slide 7

Slide 7 text

Persistent Data Structures Soluções já adotadas por praticamente todas as linguagens funcionais, como Clojure, Haskell, OCaml, etc..

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

Permite mutações, não in-place, mas sempre retornando um novo valor > var Immutable = require('immutable'); > var map1 = Immutable.Map({a:1, b:2, c:3}); Map { "a": 1, "b": 2, "c": 3 } > var map2 = map1.set('b', 50); Map { "a": 1, "b": 50, "c": 3 } > map1.get(‘b'); 2 > map2.get(‘b'); 50

Slide 10

Slide 10 text

Higher order functions > const list = Immutable.List([1,2,3,4,5,6,7,8,9,10]); List [1, 2, 3, 4, . . ., 9] > const list2 = list.filter(x => x % 2 === 0) .map(x => x * 2); List [4, 8, 12, 16] > const sum = list2.reduce((acc, x) => acc + x); 40 > list List [1, 2, 3, 4, . . ., 9]

Slide 11

Slide 11 text

De/Para JS Objects > var nested = Immutable.fromJS({a:{b:{c:[3,4,5]}}}); // Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } } > var deep = Immutable.Map({ a: 1, b: 2, c: Immutable.List.of(3, 4, 5) }); > deep.toObject(); { a: 1, b: 2, c: List [ 3, 4, 5 ] } > deep.toJS(); { a: 1, b: 2, c: [ 3, 4, 5 ] }

Slide 12

Slide 12 text

Performance Usando estruturas de HashMap Tries e Vector Tries, se baseando no ótimo trabalho do Chris Okasaki (Purely Functional Data Structures), é possível ter uma performance muito boa utilizando Persistent Data Structures.

Slide 13

Slide 13 text

Performance

Slide 14

Slide 14 text

Performance

Slide 15

Slide 15 text

Performance

Slide 16

Slide 16 text

Performance

Slide 17

Slide 17 text

Performance

Slide 18

Slide 18 text

Performance

Slide 19

Slide 19 text

Performance

Slide 20

Slide 20 text

Equality > var map1 = Immutable.fromJS({a:{b:{c:[3,4,5]}}}); // Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } } > var map2 = Immutable.fromJS({a:{b:{c:[3,4,5]}}}); // Map { a: Map { b: Map { c: List [ 3, 4, 5 ] } } } > Immutable.is(map1, map2) true > Immutable.is(map1, Immutable.Map({a:1, b:2}) false

Slide 21

Slide 21 text

Batching Mutations If a tree falls in the woods, does it make a sound? If a pure function mutates some local data in order to produce an immutable return value, is that ok? — Rich Hickey, Clojure

Slide 22

Slide 22 text

Batching Mutations > const list1 = Immutable.List.of(1,2,3); //list nesse caso é uma lista mutável //o retorno de withMutations é imutável > const list2 = list1.withMutations(function (list) { list.push(4).push(5).push(6); }); > assert(list1.count() === 3); > assert(list2.count() === 6); Aplicando uma mutação para criar um novo objeto imutável tem um overhead, que pode causar uma leve queda de performance caso sejam feitas muitas alterações frequentemente. Se você precisa aplicar uma série de mutações localmente antes de retornar, Immutable lhe dá a capacidade de criar uma cópia temporária mutável (transitório) de uma coleção e aplicar um lote de mutações de uma forma performática usando withMutations. Na verdade, isso é exatamente como o próprio Immutable aplica mutações complexas.

Slide 23

Slide 23 text

React + Redux + ImmutableJS === ♥

Slide 24

Slide 24 text

Immutable Single Store Redux abraça a ideia de manter o estado da aplicação explícito e imutável, usando funções puras (sem side-effects lembra?) sempre que possível. E todo o estado da aplicação existe em um único Atom: um objeto com valores aninhados contendo todos os dados necessários para a aplicação.

Slide 25

Slide 25 text

Rollback de alterações é fácil Como as estruturas de dados são persistentes, e as versões antigas dos dados são mantidas, é bem fácil desfazer alguma operação. Basta setar o estado global da aplicação para seu valor anterior.

Slide 26

Slide 26 text

Pure Components shouldComponentUpdate: function(nextProps) { return !Immutable.is(nextProps.value, this.props.value ); } Comparar se dois valores imutáveis são iguais é muito fácil, e isso facilita bastante na hora de ter componentes puros em React. Componentes puros em React são aqueles que, dado os mesmos props e state, não precisam ser re-renderizados. Isso pode dar um grande aumento de desempenho

Slide 27

Slide 27 text

Desvantagens

Slide 28

Slide 28 text

JS Objects Interop Immutable.Map não dá para usar map.property, e sim map.get(‘property’) Usar Immutable.Record ajuda, pois ele dá suporte a value.property, e é imutável. List não dá para usar list[0], e sim list.get(0) Libs de terceiros não são compativeis com Immutable values. `toJS()` nessa hora ajuda bastante. As vezes pode ser dificil saber se está trabalhando com um JS object, ou um Immutable object. Comentários ajudam. Usar um type-system (Typescript ou Flow) também podem ajudar bastante.

Slide 29

Slide 29 text

Perguntas?

Slide 30

Slide 30 text

Obrigado @breno_ferreira