Então você quer ser um Programador Funcional? (Parte 2)
Dar o primeiro passo para entender os conceitos de Programação Funcional é o mais importante e algumas vezes o passo mais díficil. Mas isto não tem de ser assim. Não com a perspectiva correta.
Lembrete Amigável
Por favor leia o código lentamente. Tenha certeza de ter entendido antes de seguir em frente. Cada seção é baseada na anterior.
Se você se apressar, pode perder alguma coisa que será importante mais tarde.
Refatoração
Vamos pensar em refatoração por um minuto. Aqui vai um pouco de código Javascript:
Todos nós já escrevemos código como este ao longo do tempo, começamos a reconhecer que estas duas funções são praticamente iguais e diferenciam-se somente por algumas coisas (mostradas em negrito).
Ao invés de copiar validadeSsn, colar e editar para criar validatePhone, nós deveríamos criar uma única função e parametrizar as coisas que nós editamos depois de colar.
Neste exemplo, nós passaríamos como parâmetro o value, a regular expression e a mensagem (pelo menos a última parte da mensagem).
O código refatorado:
Os parâmetros ssn e phone no código antigo, agora são representados pelo value.
As expressões regulares /^\d{3}-\d{2}-\d{4}$/ e /^(\d{3})\d{3}-\d{4}$/ são representadas pelo regex.
E por último, a última parte da mensagem SSN e 'Phone Number' são representados pelo type.
Ter uma função é muito melhor do que ter duas funções. Ou pior, três, quatro ou dez funções. Isto mantém seu código limpo e fácil de manter.
Por exemplo, se houver um bug, você precisa arrumar somente em um lugar versus procurar por todo o codebase para para achar o lugar onde esta função PODE ter sido posta e modificada.
Mas o quê acontece quando você tem a seguinte situação:
Aqui parseAddress e parseFullName são funções que recebem uma string e retornam true se ela for analisada.
Como refatoramos isto?
Bem, nós podemos usar value para address e name, e type para 'Address' e 'Name' como fizemos antes mas existe uma função onde nossa expressão regular costumava estar.
Se nós somente pudéssemos passar uma função como parâmetro...
High-Order Functions
Muitas linguagens não suportam passar funções como parâmetros. Algumas sim mas elas não facilitam.
Em Programação Funcional, uma função é um cidadão de primeira classe da linguagem. Em outras palavras, uma função é somente outro valor.
Uma vez que função são somente valores, podemos passá-las como parâmetros.
Mesmo que a Javascript não seja uma Linguagem Puramente Funcional, você pode fazer algumas operações funcionais com ela. Então aqui estão as últimas duas funções refatoradas em uma única função passando a função de parse como parâmetro chamado parseFunc:
Nossa nova função é chamada de High-order Function.
High-order Functions também recebe funções como parâmetros, retorna funções ou ambas.
Agora podemos chamar nossa high-order function para as 4 funções anteriores (isto funciona na Javascript porque Regex.exe retorna um valor verdadeiro quando uma combinação é encontrada):
validateValueWithFunc('123-45-6789', /^\d{3}-\d{2}-\d{4}$/.exec, 'SSN');
validateValueWithFunc('(123)456-7890', /^\(\d{3}\)\d{3}-\d{4}$/.exec, 'Phone');
validateValueWithFunc('123 Main St.', parseAddress, 'Address');
validateValueWithFunc('Joe Mama', parseName, 'Name');
Isto é muito melhor do que ter 4 funções identicas.
Mas note as expressões regulares. Elas são um pouco verbosas. Vamos limpar nosso código refatorando-o:
Assim está melhor. Agora quando queremos analisar um número de telefone, não precisamos copiar e colar a expressão regular.
Mas imagine que temos mais expressões regulares para analisar, não somente parseSsn e parsePhone. Cada vez que criamos um analisador de expressão regular, temos que lembrar de adicionar o .exec no final. E acredite, isto é fácil de esquecer.
Podemos nos proteger disso criando uma high-order function que retorna a função exec:
Aqui, makeRegexParser recebe uma expressão regular e retorna a função exec, que recebe uma string. validateValueWithFunc passará a string, value, para a função de parse, isto é exec.
parseSsn e parsePhone são efetivamente as mesmas de antes, a função exec da expressão regular.
Isto é uma ligeira melhoria, mas é mostrada aqui para dar um exemplo de high-order function que retorna uma função.
Contudo, você pode imaginar os benefícios de fazer esta mudança caso makeRegexParser fosse mais complexa.
Aqui está um outro exemplo de high-order function que retorna uma função:
function makeAdder(constantValue) {
return function adder(value) {
return constantValue + value;
};
}
Aqui temos makeAdder que recebe constantValue e retorna adder, uma função que irá adicionar essa constante para qualquer valor que for passado.
Aqui está como pode ser usada:
var add10 = makeAdder(10);
console.log(add10(20)); // prints 30
console.log(add10(30)); // prints 40
console.log(add10(40)); // prints 50
Nós criamos uma função, add10, passando a constante 10 para makeAdder que retorna a função que irá somar 10 para tudo.
Perceba que a função adder tem acesso a constantValue mesmo depois de makeAdder retorna. Isto é porque constantValue estava em seu escopo quando adder foi criado.
Este comportamento é bastante importante porque sem ele, funções que retornam funções não seriam muito úteis. Então é importante entendermos como elas funcionam e como este comportamento é chamado.
Este comportamento é chamado de Closure.
Closures
Aqui está um exemplo planejado para mostrar o uso de closures:
Neste exemplo, child tem acesso as suas variáveis, as variáveis de parent e grandParent.
A parent tem acesso as suas variáveis e as da grandParent.
A grandParent tem acesso somente as suas variáveis.
(Veja a pirâmide acima para esclarecimento.)
Aqui está um exemplo de seu uso:
var parentFunc = grandParent(1, 2); // returns parent()
var childFunc = parentFunc(11, 22); // returns child()
console.log(childFunc(111, 222)); // prints 738
// 1 + 2 + 3 + 11 + 22 + 33 + 111 + 222 + 333 == 738
Aqui, parentFunc mantém o escopo de parent vivo visto que grandParent retorna parent.
Do mesmo modo, childFunc mantém o escopo de child vivo visto que parentFunc que é apenas parent, retorna child.
Quando uma função é criada, todas as variáveis em seu escopo em tempo de criação são acessíveis a ela pelo tempo de vida da função. Uma função existe enquanto houver uma referência à ela. Por exemplo, o escopo de child existe enquanto childFunc referenciá-lo.
Uma closure é um escopo de uma função que é mantido vivo por uma referência àquela função.
Note que na Javascript, closures são problemáticas visto que as variáveis são mutáveis, isto é, elas podem mudar de valor do tempo em que elas foram "enclausuradas" para o tempo que a função retornada é chamada.
Agradecidamente, variáveis em Linguagens Funcionais são imutáveis, eliminando esta fonte comum de bugs e confusão.
Artigo original: So You Want to be a Functional Programmer (Part 2)