Skip to content

Instantly share code, notes, and snippets.

@MarcoWorms
Created October 11, 2017 17:36
Show Gist options
  • Save MarcoWorms/df029d1556d63e6f25f77fbf1bc66057 to your computer and use it in GitHub Desktop.
Save MarcoWorms/df029d1556d63e6f25f77fbf1bc66057 to your computer and use it in GitHub Desktop.

Imutabilide

Marco Guaspari Worms - Pagar.me & Catnigiri

// Jupyter notebook é massa
{
  var obj = {
    a: 1,
    b: 3,
  }
  console.log(obj)
}
{ a: 1, b: 3 }
// Aqui fora fica pro resto da apresentação
var pretty = object => JSON.stringify(object, null, 2)

// Aqui dentro não
{
  var obj = {
    a: 1,
    b: 2,
  }
  console.log(pretty(obj))
}
{
  "a": 1,
  "b": 2
}
// Números são ímutáveis
{
  const a = 3
  a = 2
}
evalmachine.<anonymous>:4

  a = 2

    ^



TypeError: Assignment to constant variable.

    at evalmachine.<anonymous>:4:5

    at ContextifyScript.Script.runInThisContext (vm.js:44:33)

    at Object.runInThisContext (vm.js:116:38)

    at run ([eval]:617:19)

    at onRunRequest ([eval]:388:22)

    at onMessage ([eval]:356:17)

    at emitTwo (events.js:125:13)

    at process.emit (events.js:213:7)

    at emit (internal/child_process.js:774:12)

    at _combinedTickCallback (internal/process/next_tick.js:141:11)
// Strings também
{
  const a = "something"
  a += ' boop'
}
evalmachine.<anonymous>:4

  a += ' boop'

    ^



TypeError: Assignment to constant variable.

    at evalmachine.<anonymous>:4:5

    at ContextifyScript.Script.runInThisContext (vm.js:44:33)

    at Object.runInThisContext (vm.js:116:38)

    at run ([eval]:617:19)

    at onRunRequest ([eval]:388:22)

    at onMessage ([eval]:356:17)

    at emitTwo (events.js:125:13)

    at process.emit (events.js:213:7)

    at emit (internal/child_process.js:774:12)

    at _combinedTickCallback (internal/process/next_tick.js:141:11)

O valor de String, Number, e Booleans são imutáveis.

É impossível mudar o valor de uma const desses tipos.

// Com objetos temos uma situação triste :(
{
  const transaction = {
    amount: 1000, // $ 10.00
    customer: {
      name: 'Michael',
    },
  }
  
  transaction.amount = 3000

  console.log(pretty(transaction))
}
{
  "amount": 3000,
  "customer": {
    "name": "Michael"
  }
}
// Com arrays também :(
{
  const transactions = [
    { amount: 1000 },
    { amount: 2000 },
  ]
  
  transactions.push({ amount: 3000 })

  console.log(pretty(transactions))
}
// Mas pq isso é ruim?
[
  {
    "amount": 1000
  },
  {
    "amount": 2000
  },
  {
    "amount": 3000
  }
]

view = render(state)

this.setState(newState)

const React = require('react')

class Something extends React.Component {
  constructor (props) {
    super(props)
    this.state = { // também conhecido como initialState
      message: 'Hello',
    }
    this.changeMessage = this.changeMessage.bind(this)
  }
    
  changeMessage () {
    this.setState({ message: 'World' }) //
  }
    
  render () {
    return (
      <div>
        <button onClick={changeMessage} />
        <p>{this.state.message}</p>
      </div>
    )
  }
}
// Shallow merge
var merge = (x, y) => Object.assign({}, x, y)

{
  const state = {
    session: 12345,
    user: {
      name: 'José'
    }
  }
  
  const newState = merge(state, { session: 99999 })
  
  // this.setState(newState)
  
  console.log(pretty(newState))
}
{
  "session": 99999,
  "user": {
    "name": "José"
  }
}
this.setState({ session: 99999 })

// same as
    
this.setState(merge(state, { session: 99999 }))
// Shallow merge não ajuda sempre
{
  const state = {
    session: 12345,
    user: {
      name: 'José'
    }
  }
  
  const newState = merge(state, {
    user: { country: 'Brasil' }
  })
  
  console.log(pretty(newState))
}
{
  "session": 12345,
  "user": {
    "country": "Brasil"
  }
}
// Merge dentro de merge começa a complicar a leitura
{
  const state = {
    session: 12345,
    user: {
      name: 'José'
    }
  }
  
  const newState = merge(state, {
    user: merge(state.user, { country: 'Brasil' })
  })
  
  console.log(pretty(newState))
}
// Deep merge pode resolver mas é um overkill de performace
// Immutable é massa
// https://facebook.github.io/immutable-js/
{
  const { Map } = require('immutable')
  
  const state = Map({
    session: 12345,
    user: {
      name: 'José'
    }
  })
  
  const newState = state.set('session', 321)
  
  //  this.setState(newState)
  
  console.log(pretty(newState))
}
{
  "session": 321,
  "user": {
    "name": "José"
  }
}
// Tem que estruturar tudo com a os tipos dele :(
{
  const { Map } = require('immutable')
  
  const state = Map({
    session: 12345,
    user: Map({
      name: 'José'
    })
  })
  
  const newState = state.setIn(['user', 'country'], 'Brasil')
  
  //  this.setState(newState)
  
  console.log(pretty(newState))
}
{
  "session": 12345,
  "user": {
    "name": "José",
    "country": "Brasil"
  }
}
// Lenses!
// http://ramdajs.com/
{
  const { lensPath, set } = require('ramda')
  const state = {
    user: {
      name: 'José',
      phone: {
        ddd: 11,
        number: 12345678,
      },
    },
  }
  const dddLens = lensPath(['user', 'phone', 'ddd'])
  const newState = set(dddLens, 12, state)
  
  console.log(pretty(newState))
}
{
  "user": {
    "name": "José",
    "phone": {
      "ddd": 12,
      "number": 12345678
    }
  }
}
{
  const { lensPath, over } = require('ramda')
  const state = {
    session: 12345,
    user: {
      name: 'José',
      phone: {
        ddd: 11,
        number: 12345678,
      },
    },
  }
  const dddLens = lensPath(['user', 'phone', 'ddd'])
  const double = x => x * 2
  
  const newState = over(dddLens, double, state)
  
  console.log(pretty(newState))
}
{
  "session": 12345,
  "user": {
    "name": "José",
    "phone": {
      "ddd": 22,
      "number": 12345678
    }
  }
}
// o setState pode receber uma função ao invés de um novo estado!

const newState = over(dddLens, double, state)

this.setState(newState)

// same as

this.setState(state => over(dddLens, double, state))
this.setState(state => over(dddLens, double, state))

// same as (Ramda bonus)

this.setState(over(dddLens, double))
// uma nota sobre aplicação parcial de funções
{
  const { add } = require('ramda')
  
  console.log(add(2, 3))
  
  const add2 = add(2)
  
  console.log(add2(3))
}
5
5
// map, filter, e reduce: adeus 'for', 'push', e seus amigos mutáveis
{
  const transactions = [
    { amount: 1000, status: 'paid' },
    { amount: 2000, status: 'refused' },
    { amount: 3000, status: 'paid' },
    { amount: 4000, status: 'pending' },
    { amount: 5000, status: 'refused' },
  ]

  const paid = transactions.filter(transaction => transaction.status === 'paid')
  
  console.log(pretty(paid))
}
[
  {
    "amount": 1000,
    "status": "paid"
  },
  {
    "amount": 3000,
    "status": "paid"
  }
]
// Ramda pode nos ajudar de novo!
{
  const { propEq, filter } = require('ramda')

  const transactions = [
    { amount: 1000, status: 'paid' },
    { amount: 2000, status: 'refused' },
    { amount: 3000, status: 'paid' },
    { amount: 4000, status: 'pending' },
    { amount: 5000, status: 'refused' },
  ]

  const paid = transactions.filter(propEq('status', 'paid'))
  // same as
  // const paid = transactions.filter(transaction => transaction.status === 'paid')
  
  console.log(pretty(paid))
}
[
  {
    "amount": 1000,
    "status": "paid"
  },
  {
    "amount": 3000,
    "status": "paid"
  }
]
// Imagine um banco de dados onde só podemos inserir
// operações novas e nunca mexer nas que existem
{
  const balanceOperations = [
    { id: 0, amount: 10000 },
    { id: 1, amount: -5000 },
    { id: 2, amount: 2000 },
    { id: 3, amount: -4500 },
  ]

  const balance = balanceOperations.reduce(
    (balance, bo) => balance + bo.amount,
    0
  )
  
  console.log(balance)
}
2500
// E aquela pitada de ramda
{
  const { prop, add } = require('ramda')
    
  const balanceOperations = [
    { id: 0, amount: 10000 },
    { id: 1, amount: -5000 },
    { id: 2, amount: 2000 },
    { id: 3, amount: -4500 },
  ]

  const balance = balanceOperations
    .map(prop('amount')) // [ 10000, -5000, 2000, -4500 ]
    .reduce(add, 0)
  
  console.log(balance)
}
2500
// Encadeando operações
{
  const result = [1, 2, 3]
    .map(x => x * 2)
    .map(x => x + 5)
    .map(x => x * 9)
  //////////////////////////////////////////////////////////////
  const { map, multiply, add, pipe } = require('ramda')
  
  Promise.resolve([1, 2, 3])
    .then(map(multiply(2)))
    .then(map(add(5)))
    .then(map(multiply(9)))
    
  //////////////////////////////////////////////////////////////
    
  const operations = pipe(
    map(multiply(2)),
    map(add(5)),
    map(multiply(9))
  )
  
  operations([1, 2, 3])
}

Uma função pura deve:

  • Sempre acessar APENAS variáveis que foram enviadas para ela.

  • Sempre retornar alguma coisa.

  • Nunca alterar o valor das variáveis enviadas (imutabilidade).

  • Sempre para os mesmos valores de entrada você terá os mesmos valores de saída

// Reducers são funções puras que recebem
// o estado e uma ação e retornam um novo estado
var reducers = (state = { counter: 0 }, action) => {
  if (!action) { return state }
  switch (action.type) {
    case 'INCREMENT':
      return { counter: state.counter + 1  }
    case 'DECREMENT':
      return { counter: state.counter - 1  }
    default:
      return state
  }
}

console.log(reducers())
console.log(reducers({ counter: 1 }))
console.log(reducers({ counter: 4 }, { type: 'INCREMENT' }))
{ counter: 0 }
{ counter: 1 }
{ counter: 5 }
// A store é criada usando os reducers
var createStore = reducers => {
  var state = reducers()
  const store = {
    dispatch: (action) => {
      state = reducers(state, action) 
    },
    getState: () => {
      return state
    } 
  }
  return store
}

var store = createStore(reducers)

store.getState() // => { counter: 0 }

// Despachamos ações para a store e deixamos
// que ela faça o trabalho de alterar o estado
store.dispatch({ type: 'INCREMENT' })

store.getState() // => { counter: 1 }
{ counter: 1 }

Thanks!

Telegram: @marcoworms

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment