O que é um paradigma de programação?
No começo de qualquer curso na área de desenvolvimento é ensinado o conceito de algoritmo, e então surge aquela clássica explicação de que para resolver um problema deve-se criar um fluxo de atividades e executar etapa por etapa. Por exemplo, se o objetivo é calcular o resultado obtido por um estudante no final do semestre, o professor pode propor: somem as notas X e Y, e depois dividam esse valor por dois. Se a soma for maior que sete o aluno está aprovado, caso contrário, será reprovado. Mesmo sem saber, o aluno está sendo instruído a seguir um paradigma de programação. Assim como as linguagens naturais possuem similaridades entre elas, as linguagens de programação também podem ser categorizadas conforme sua sintaxe. Um paradigma está ligado a visão que o programador tem sobre o problema e como ele pensa em resolvê-lo. Podemos dizer ser a forma como se programa. No exemplo destacado, de maneira bem básica, somos apresentados ao chamado paradigma procedural, em que o programa descreve uma lista de instruções que devem ser seguidas em ordem pela máquina. Linguagens como C, C++ e Pascal estão dentro dessa categoria. À medida que entra em novos projetos e procura novas soluções, o programador se depara com outros paradigmas, como o orientado a objetos e o funcional. O primeiro se destacou em linguagens como Java e C# e já faz parte do ferramental da maioria dos profissionais da área. O último, por muito tempo, foi encarado como um assunto acadêmico, mas está ganhando cada vez mais espaço no mercado de trabalho contemporâneo.Quando os problemas surgem
Nem tudo são flores na vida de um desenvolvedor, e conforme o sistema em que trabalha cresce, vulnerabilidades também começam a surgir. Alguns desses problemas envolvem comportamentos inesperados, mudanças de estado que afetam mais de um componente e fazem com que algo indesejado aconteça. Além disso, com a expansão de dispositivos conectados à internet, é natural também que um sistema tenha que suportar cada vez mais usuários conectados, além de saber lidar com dados concorrentes. Por exemplo, ele precisa gerenciar a manipulação de uma informação compartilhada entre duas ou mais conexões. Trabalhar com concorrência em linguagens orientadas ao objeto é complexo e custoso, já nas linguagens funcionais existem princípios que ajudam a economizar boas linhas de código. O artigo intitulado The Free Lunch is Over, publicado em 2005 por Herb Sutter, mostra que esse tema merece nossa atenção. Ele conta que por muitos anos alguns programadores aproveitaram a constante evolução na performance dos processadores, esperando que a velocidade de seus sistemas também aumentasse de maneira orgânica, sem maiores esforços. Mas a verdade é que existem obstáculos físicos (até então) que colocam um limite na velocidade dos processadores, por isso, a tendência é que venham com mais núcleos (cores) e que os processos sejam distribuídos entre eles. O software que se aproveita desse fato para executar tarefas paralelas e concorrentes está mais preparado para a tecnologia atual e futura. Portanto, engana-se quem pensa que concorrência é um assunto dedicado a programadores seniores que trabalham em grandes projetos.Conceitos fundamentais de Programação Funcional
Vejamos agora algumas das características do estilo de Programação Funcional. Para isso, utilizaremos o JavaScript, que é uma linguagem multiparadigma e amplamente utilizada.Funções puras e impuras
Primeiramente, o que é uma função? Recordando os conceitos matemáticos, podemos pensar como uma expressão em que dado o valor X, ele retorna Y. X não retorna Z, mas apenas Y. Pense em uma função chamada ‘dobrar’, em que ela recebe um valor e retorna este multiplicado por dois. Caso for enviado o valor ‘2’, o único retorno possível é ‘4’, e nada mais. Por meio disso, entendemos outra definição importante, o de funções puras e impuras. Uma função que é pura segue a lógica já explanada, em que só existe um retorno para um determinado valor. Isso evita os chamados efeitos colaterais, que nada mais são do que interações com o ambiente fora da função e que podem gerar comportamentos indesejados. A imagem abaixo ilustra bem esse cenário. No primeiro caso a função ‘dobrar’ pode ser classificada como impura: ela altera um valor de fora do escopo, diferente do segundo caso, que utiliza de um argumento para gerar um novo valor. O terceiro caso já mostra um exemplo mais comum, o de salvar informações no banco de dados. Pensemos no seguinte: é possível garantir que o retorno sempre será o mesmo? Como existe uma comunicação com algo externo à função, no caso a conexão com o banco de dados, então irão existir efeitos colaterais que podem mudar o valor do retorno. Em um momento pode ser armazenado com sucesso, em outro pode haver uma perda de conexão, dentre outras situações. Logo, essa função também é categorizada como impura.Imutabilidade
Além das funções impuras, ter um estado compartilhado é outro ponto sensível numa aplicação, conforme já explanado anteriormente. Mas, e se esse estado não estiver sincronizado entre as partes que o consomem? O paradigma funcional também propõe a imutabilidade, ou seja, evitar o uso de variáveis ou alterar as propriedades de um objeto. Quando é necessária uma alteração de estado, nunca se altera o seu valor, mas sim cria-se um novo. Para isso, muitas linguagens adotam o conceito de constantes. A partir da versão ES2016 do JavaScript, foi adicionado a palavra-chave const, que impede que um dado uma vez atribuído receba um novo valor.Programação declarativa e a importância das funções
Unido também ao objetivo de tornar um código mais legível, o código programado de forma declarativa usa de funções e composição de funções para explicitar o que o programa está fazendo. Isso difere de um código imperativo, em que o programador define o passo a passo que a máquina deve tomar. De maneira resumida, um código imperativo é escrito mostrando como chegar a um objetivo, já um código declarativo mostra o que está acontecendo. Assim, é muito mais fácil dar manutenção a um código claro e explícito, do que ler um programa em que se precisa percorrer as variáveis do sistema para entender o que está acontecendo. Essa abordagem se harmoniza muito bem com o paradigma funcional, pois essas linguagens provêm funções especiais em que é possível realizar o encadeamento de processos e a transformação de dados. É o caso, por exemplo, de três funções bem conhecidas: map, filter e reduce, responsáveis respectivamente por transformar, filtrar e reduzir uma lista ou vetor. Veja nos exemplos abaixo como elas podem ser usadas para melhorar a legibilidade. Digamos que o objetivo seja formar uma frase com palavras capitalizadas: “Programação Funcional é vida”. Ao ver o primeiro exemplo, fica o questionamento: ao ter a primeira impressão do código, fica claro o que ele está fazendo? Não concorda que precisaríamos analisar o loop, as funções e as variáveis com cuidado para entender melhor? O código recorre a um baixo nível para chegar no resultado, até mesmo manipulando o índice e a posição do vetor. Por outro lado, é um código que utiliza das funções especiais comentadas. Já no exemplo acima, a situação é diferente. As funções deixam explícita qual é a intenção do código e os passos são definidos por meio do encadeamento de processos. Primeiramente, ele filtra apenas letras e então transforma cada palavra ao capitalizá-la, depois reduz a lista de palavras a uma única cadeia de caracteres e, por último, remove espaços desnecessários. No último caso também vemos uma demonstração de dois conceitos importantes para o paradigma: (1) First Class Functions: uma função é tratada como tipo e, portanto, pode ser armazenada também em uma variável/constante. No exemplo, as constantes ‘Only Letters’, ‘capitalized’ e ‘concatWords’ referenciam funções. (2) Higher Order Functions: uma função pode receber outra função como argumento ou retornar uma nova. É o que acontece nas funções filter, map e reduce, que possuem as funções mencionadas como parâmetros.Indo além
Os princípios abordados não são restritos a um número pequeno de linguagens e podem ser usados como guias mesmo naquelas mais procedurais. Porém, o potencial é maior quando se utiliza em uma linguagem de Programação Funcional, que está equipada por completo para tratar esses cenários. Dentre algumas delas, estão Clojure, Scala, Haskell e F#. Porém, vale destacar uma que foi criada por um brasileiro e está ganhando bastante adoção: o Elixir. Conforme o próprio site diz, o “Elixir é uma linguagem de Programação Funcional dinâmica, feita para construir aplicações escaláveis e de fácil manutenção”. Sua sintaxe simples e funcionalidades como o pattern matching facilitam a legibilidade do código. Já a tolerância a falhas (fault tolerance) e o modelo de atores (actor model) tornam natural programar visando concorrência e distribuição. Não é por nada que grandes empresas como o Pinterest e Discord estão utilizando essa tecnologia e têm seus cases publicados em blogs. Logo, Elixir é uma boa indicação para aqueles que querem explorar mais o paradigma. Por fim, significa então que todo programador deve dominar conhecimentos em Programação Funcional? Não, pois todos são considerados meios para chegar a um fim. Mas, qual desses meios gera maiores desafios? Cabe ao profissional decidir levando em conta o contexto do projeto e suas aspirações pessoais.Na SoftDesign, Pessoas Programadoras participam desde a fase de experimentação até o Desenvolvimento de Software. Para isso, profissionais competentes, colaborativos e com conhecimentos em diversas linguagens de programação são essenciais. Logo, se você precisa de ajuda para desenvolver produtos de sucesso, entre em contato conosco por meio do formulário abaixo.
Vamos conversar?
Entre em contato e vamos conversar sobre seus desafios de TI.