Imutabilidade? Eis a questão!
A Programação Funcional tende a intimidar as pessoas com os termos acadêmicos emprestados da Teoria das Categorias para descrever interfaces conhecidas como “Estruturas Algébricas”.
O Paradigma Funcional foi adotado através de linguagens como Erlang, R e até as mais recentes ML, Haskell, OCaml, F# e Elixir. Já o nosso amado Javascript não foi projetado para ser uma linguagem funcional. Felizmente, o JavaScript é bem adequado para a tarefa, graças às funções de primeira classe(first-class) e de alta ordem(high-order), além da adequada otimização de chamada(tail-call optimization) no ES2015.
Quanto mais abstrato é um conceito, mais difícil é entender. Interfaces são abstratas por natureza. Quanto mais abstratas elas são, mais poderosas se tornam, porque nos permitem resolver uma gama mais ampla de problemas aparentemente não relacionados com um conjunto surpreendentemente pequeno de padrões.
O objetivo da programação funcional é permitir que pensemos menos e escrevamos mais códigos descritivos.
A boa notícia é que você não precisa virar a chave e tudo está funcionando na sua cabeça. Você pode aplicar abordagens funcionais à medida que vai aprendendo. Eu venho lentamente estudando esse paradigma e cada dia que passa vejo novas formas de resolver problemas e a cada refatorada se torna muito mais agradável o código.
Aprender novos Paradigmas de Programação é como aprender um novo instrumento. A cada nota aprendida aumentamos nossos poderes de expressão.
No meu dia-a-dia no trabalho utilizo com pouca frequência o Paradigma Funcional mas esse embasamento me permite enxergar os problemas de uma forma diferente e até unir conceitos funcionais com outros paradigmas como a Orientação a Objetos. Isso tem sido uma tendência na evolução das linguagens, ou seja, podemos unir o melhor dos mundos. O exemplo abaixo em Typescript/Angular mostrar a união de conceitos funcionais como map e conceitos de Orientação a Objeto como o uso de classes.
Aprender vários paradigmas nos ajuda a aplicar maneiras diferentes de pensar sobre soluções para problemas, porque as soluções variam entre as linguagens.
Em última análise, veremos que uma aplicação sem estado é inútil. Mas sempre precisamos selecionar criteriosamente os pontos do código onde permitimos que a mutação ocorra. A maioria das partes internas do código deve se esforçar para ser imutável e conseqüentemente testável. Quanto mais mutações você puder eliminar, melhor será.
Por que devemos nos importar com a imutabilidade?
Os fabricantes de chips estão nos limites atômicos da miniaturização de transistores. Como resultado, os tick de clock não aumentaram desde 2004.
O multi-core é atualmente a estratégia preferida para melhoraramos a velocidade. As rotinas com estado mutável compartilhado não podem ser distribuídas em paralelo em vários núcleos porque não são tão seguras quanto uma thread.
Como sugestão segue abaixo a palestra de Robert Martin sobre esse assunto. Apenas observe: uma “declaração de atribuição” é totalmente benigna, uma “declaração de reatribuição” é problemática.
Quando você reatribui uma variável, você está introduzindo a mudança de estado em sua aplicação. Para reproduzir um bug complexo, você geralmente precisa da sequência de cálculos que levam à falha. Quando você altera variáveis, você está jogando fora essa sequência.
Como evitamos as mutações?
Sempre que precisar modelar uma mudança de estado, você passa o valor anterior para uma função que retorna um novo valor. Não mude o valor antigo apenas retorne um novo.
Manual
Embora o JavaScript não tenha suporte para objetos imutáveis, ainda podemos escrever nosso código de uma maneira que evite a maioria das mutações.
Não mude objetos em funções
Escrever funções que retornam cópias alteradas em vez de alterar as propriedades do objeto especificado.
Não troque objetos após a construção.
Objetos são referências, se evitarmos mudar suas propriedades, evitamos situações de estados pouco claros. Também o nosso código finalizado será mais simples de entender e mais fácil de testar.
Parece um monte de trabalho tedioso para evitarmos a mutação com os exemplos acima mas o fato de o JavaScript não ter um suporte embutido para objetos imutáveis torna isso muito difícil para nós. Hora de conseguir ajuda!
Immutable.js
O Immutable.js do Facebook é uma pequena biblioteca que nos ajuda a manter nosso estado imutável. Existem outras bibliotecas que funcionam de maneira semelhante (Mori, seamless-immutable, acient-oak), mas para este post nós nos limitamos ao Immutable.js.
Eu não vou entrar em detalhes porque a documentação é muito simples:
O Immutable.js fornece muitas estruturas de dados imutáveis persistentes, incluindo: Lista, Pilha, Mapa, Mapa Ordenado, Set, Set Ordenado e Record.
Ou em forma de código:
Apesar de perdermos o acesso direto às propriedades do objeto, agora podemos nos concentrar em nossos objetivos, em vez de lutar contra mutações.
ESLint Immutable
Existe também o plugin eslint-plugin-immutable tem como finalidade desativar todas as mutações no JavaScript. O readme menciona muito React/Redux, mas você pode seguramente usar o plugin sem os dois.
O plugin adiciona três regras:
- no-let: use const ao invés de let.
- no-this: proíbe isso e, portanto, classes ES6.
- no-mutation: proíbe atribuir um valor ao resultado das expressões de membro.
Quando utilizar?
Concorrência
Este é o principal motivo pelo qual a imutabilidade faz sentido. Para ser thread-safe, não é necessário bloquear algo que não seja alterado. No Javascript não há concorrência real. Em vez disso, temos um loop de eventos. Então esqueça isso.
Para evitar efeitos colaterais.
Alterar propriedades de objetos é considerado uma mutação, ou seja, um efeito colateral por definição. Todos nós aprendemos que devemos evitar os efeitos colaterais a todo custo. Não precisamos examinar a implementação de uma função se ela não tiver efeitos colaterais. Nosso código fica mais fácil de raciocinar e será mais previsível e mais testável.
Com programação funcional
Há momentos em que você precisa parar e pensar se está lidando com um valor ou uma referência. Objetos imutáveis são sempre valores. Este é um dos princípios fundamentais da programação funcional. Se eu passar o valor para uma função, posso prometer que permanece o mesmo para sempre.
Quando não utilizar?
Mudar propriedades com frequência
Se você tem objetos que mudam com muita frequência, não é a melhor ideia criar uma nova instância para cada alteração. Isto é especialmente verdadeiro para jogos e simulações onde isso acontece várias vezes por segundo. Não estou dizendo que você não pode usar a imutabilidade em jogos, mas é mais fácil cair em problemas de desempenho do que em objetos mutáveis.
Grandes Estruturas de dados
Se você tiver uma enorme árvore de dados armazenada como um recipiente imutável, recomendo verificar seu consumo de memória. É claro que a coleta de lixo mata todos os objetos antigos, mas a cópia de objetos grandes ainda tem seu preço (A maioria dos frameworks do mercado já lidam muito bem este tipo problema).
Conclusão
Em geral, é uma escolha de design de linguagem bastante razoável para se levar em consideração são os tipos imutáveis de dados, já que há alguns problemas que simplesmente não são bem modelados quando tudo é imutável mas maioria na das situações, as vantagens dos tipos imutáveis superam largamente as desvantagens e são melhores do que as alternativas pois tormam o código mais claro, manutenível e testável.
Como o dito no começo do texto não precisamos virar uma chave sair mudando paradigmas, podemos começar implementando tipos imutavéis e posteriormente adentrando pouco a pouco dentro de padrões mais complexos e úteis para nossa aplicação. Para finalizar esse post deixo o tweet do @jamesiry como reflexão.
Ser ou não ser um “Functional Programmer”, eis a questão, rs.
Bons estudos e até a próxima!